- ベストアンサー
main の引数には const 付けた方が
C言語での質問です。 引数を取るような main 関数は int main( int argc, char *argv[]){~} とされていますが、argvの指す文字列を変更する、というのはいくら何でもまずいので、 int main( int argc, const char *argv[]){~} あるいは int main( int argc, const char const * const * argv){~} の方がいいのではないでしょうか? 何故、constを付けない形が出回っているのでしょうか?
- みんなの回答 (13)
- 専門家の回答
質問者が選んだベストアンサー
> できれば、どういう場合に変更することに意味があるのか詳しく知りたいです。 昔、プログラムで使えるメモリが貴重だった頃は、どうやってメモリを節約するかが 重要でした。そういう世界では、入力パラメータを解析するのにメモリを消費しないで 解析するのがよいコーディングでした。 カンマ区切りのパラメータを解析する時は、strtok()を使って 入力パラメータ(argv)を分解する(区切り文字を\0で書き換えてパラメータを取り出す) というのが普通の方法でした。 strtokのマニュアルでも、サンプルソースにargvで入力されたパラメータを strtokで分解する例が書かれています。 http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/strtok.3.html 今でも組み込み系でメモリがあまり使えないところでは、こういう手法は よく使われると思います。 パラメータを分解するのに、わざわざ新しいメモリ領域を確保して別の領域に 取り出すなんて、(環境によっては)メモリの無駄遣いということです。 このために、C言語の規格でargc,argvは書き換え可能でなければならない ことが明記されています。
その他の回答 (12)
- Lchan0211b
- ベストアンサー率61% (573/930)
No.6,12です。 > うっかり if( argv[i][0] = '-' )~ と書いてしまう類のミス > コンパイラはスルーする、ということを肝に銘じて、自己責任で気をつける、というのが正解のようですね。 そういうのは、lintチェック等で誤りやすいパターンを警告する工程を ビルドプロセスの中に組み込んで、毎回自動実施するのが正解だと思います。
お礼
わざわざ、ありがとうございました。しつこい補足にも関わらず詳しく教えてくださった皆様に感謝いたします。
補足
>lintチェック等で誤りやすいパターンを警告する工程を >ビルドプロセスの中に組み込んで、毎回自動実施するのが正解 なるほど。 一応、私の状況を説明しておきますと、MS-DOS時代に、プログラマとしての訓練など受けていないのに、仕事でC言語を使うことになって(仕様書はなかったなあ)独学した者です。最近になって、特に本格的なC言語プログラムを書く必要はないのですが、正確な知識が欲しいのと、現在のC言語はどうなってるのか?(64ビットや文字コードの対応などなど)が気になって調べている状態です。 結局、参考書などでは argv はパラメータなどを「取得」するもの、としか書かれていないことと、argvの指す文字列の扱いはOS依存するだろうから、これを書き換えるのは処理系依存コードになるはずだ、という考えから、argvは「読み込み専用」と思い込んでいたようです。 私は無様なパラメータ解析コードを書いてましたが、複雑なオプションとか解析するなら、getopt(Windows環境でもやりようはある)を使う方がいいし、その肝心のgetoptがargvを弄っているのだから、ことさらにconstを付けるのは無意味ですね。 しかし、getoptのプロトタイプでは argv は char * const argv[] となってるようですが何でここにconstが入っているのだろう? ちなみに、C++としてコンパイルする場合、(char**)型の値を (const char* const *)型にキャストするのは static_cast でいいが、(char**)型の値を (const char**)型にキャストするのは const_cast でないといけないようですね(コンパイラによるかもしれないが)。const_cast は const などを外すキャストと言われますが、それだけではないのですね。 大分、事情が分かってきたので、もう少ししたら締め切らせていただきます。
- Lchan0211b
- ベストアンサー率61% (573/930)
No.6です。 main関数のパラメータにconst属性をつけるのに実質的な危険はないの かもしれませんが、一般論として、もともとconstでないものを自分が参照 しないからといってconst属性をつけた変数で処理するのは、むしろ危険 だと私は思います。 いくらconst属性をつけても、元々の領域は書き換え可能領域です。 自分が参照しかしないからと言ってconst属性をつけて処理しても、 もしかしたら自分のプログラムから呼び出した別のプログラムが その書き換え可能領域を正しく書き換えているかもしれません。 そうすると、勝手にconst属性をつけて処理しているプログラムは もしかすると最適化により、書き換えられたことを認識せず 処理してしまうかもしれません。 const属性というのは、「自分が書き換えませんよ」と宣言している ものではなく、「誰も書き換えない領域です」と宣言しているものだと 私は認識しています。
お礼
説明ありがとうございました。 >「誰も書き換えない領域です」と宣言しているものだ mainを呼ぶ、と書いてしまったのは、手が滑ってしまったミスです。すみません。他の関数がmainを呼んだからといって、プログラム名や引数を取得できるわけではありませんから。 constを付けるのが安全であり得るのは、少なくとも、mainのargvをたどる以外にコマンドライン情報のアクセスは絶対にできない場合(そこでガードできる場合)に限りますから、そんな保障はどこにもない状態では確かに危険ですね。 ありがとうございます。
補足
プロトタイプでconst付きの自作mainを作って、本来のmainのargvは外から参照できないようにすれば、自作mainの中でargvを弄る関数を呼び出すと警告(or エラー)が出るか?と思ったのですが、最適化とか考えると確かに危険ですね。というか、Cではmainを他の関数から呼ぶことができるので、ここで駄目ですね。 ちなみに、C++風キャストを試してみたら、 const_cast でないと駄目のようですから(2重ポインタの危険が大きい?)やはり、危険なキャストなんですね。 キャストなしの場合、Cだと警告は出てもコンパイルは可能でしたが、C++ではコンパイルエラーですね。 まあ、例えば、 if( argv[i][0] == '-' )~ を、うっかり if( argv[i][0] = '-' )~ と書いてしまう類のミス(プロの方々は知りませんが私はよくやります)は、コンパイラはスルーする、ということを肝に銘じて、自己責任で気をつける、というのが正解のようですね。
> この場合、余計なコマンドライン引数を取り除いた形の文字列配列を > (内容をargv内からコピーして)別に作る方法だと、支障が出るのでしょうか? 支障はないですが、私は理想論だと思います。 小さいコマンドラインプログラムをいくつもいくつも作るとき、main文に2次元配列のメモリ管理をコーディングするのは(特にエラー終了の後始末が)大変ですし、メモリの解放は忘れやすいので、逆に危険ではないでしょうか。コマンドライン引数は、OSの方でメモリ管理してくれるのですから。
お礼
ご説明、ありがとうございました。 参考書での独学では、どうしてもサンプルに「エラー処理は煩雑なので省略」したコードが多いので、エラー処理に関しては自己流になってしまうため、さらに危険ですね。
補足
>小さいコマンドラインプログラムをいくつもいくつも作るとき >特にエラー終了の後始末 >特にエラー終了の後始末 納得しました。
- Tacosan
- ベストアンサー率23% (3656/15482)
あ, 思い出した. 引数を解析するライブラリのうち, GNU の getopt は引数の順序を入れ替えてる (そうやって「オプション」を前の方に集め, 「オプションでない引数」を後ろの方にまとめてる). 以下余談: const char const * const * argv は最初の 2つの const が重複してる. ひょっとして char const * const * const argv としたかった? あと, 実は char **p; const char **q; とあると p=q; と q=p; のどっちも代入できないというわな.
お礼
情報ありがとうございました。 コマンド引数を解析する関数がarggvの内容を変更しているのであれば、constを付ける意味はないですね。
補足
>ひょっとして >char const * const * const argv >としたかった? あ、そうでした。すみません。・・・でも何でconstの重複も警告出なかったのかな? >とあると p=q; と q=p; のどっちも代入できないというわな. めもめも。型が違うと見なされるか。キャストすればできるだろうが、constのないものをconst付きにキャストするのはまだ安全性が高いけど、逆はやばいな。確かC++では制限があるんでしたよね。 どうしても、「このプログラムではargvは変更しません」と宣言することに拘るなら、 int my_main( int argc, const char **argv ); int main( int argc, char **argv){ return my_main( argc, (const char **)argv ); } ぐらいか。これはこれで気持ち悪いけど、むしろこれで想定外の動作とかしないかがちょっと心配。 関係ないですが、Objective-C では const char *argv[] のようですね。
No.7です。 > 仮効率より信頼性が重視される現在は、引数を変更するのは好ましくない、 おっしゃる通りだと思います。私もargcやargvを除いて、まず仮引数を書き換えません。 > int i; i=argc; とかになるのでしょうか。 GLUT(OpenGLのツールキット)のような、独自のコマンドライン解析を行うようなライブラリを用いる場合、余計なコマンドライン引数をあらかじめ取り除いてからライブラリに引き渡す方が安全な場合もあるでしょう。 何を安全とするかの方針次第にもよりますが。 > constを付けることはいけないことなのでしょうか? No.8さんの回答の通りかと。
お礼
いろいろな場合を考えてくださり、ありがとうございました。 勉強になります。
補足
>余計なコマンドライン引数をあらかじめ取り除いてからライブラリに引き渡す方が安全な場合もあるでしょう。 この場合、余計なコマンドライン引数を取り除いた形の文字列配列を(内容をargv内からコピーして)別に作る方法だと、支障が出るのでしょうか?
- Tacosan
- ベストアンサー率23% (3656/15482)
#1 では main の形式として int main(void) int main(int argc, char * argv[]) の 2つが挙がっていますが, 規格上これらに加えて 処理系で認められた形 も許されています (あたりまえだが). なので, あなたの使う処理系で int main(int argc, const char *argv[]) が許されているのであればそのようにしてもかまいません (ただし「一般的に使えるわけではない」ことを認識しておく必要はあります). あと規格上 char ** と const char ** が互換じゃない という微妙なところも理解しておくべきかな. Unix だと ps で argv が見えるので, argv[0] をいじることにより「今こんなことしてますよ~」と見せるデーモンもあったような気がする. sendmail とか, そうじゃなかったかなぁ.
お礼
丁寧な説明、ありがとうございました。 argv[0]を書き換えて、シェルに情報を伝える、ということは、argvはコマンドライン情報の参照渡し(をポインタを使ってシミュレートしたもの)と考えた方が自然かもしれませんね。
補足
gccでオプションなしでやった場合は、const char const * const * argv というところまでやっても警告も何も出なかったのですが、VC++では警告が出ましたので、原則として、やはり仕様としてはやってはいけないことなんだな、と納得しました。移植性が犠牲になっては元も子もないですから。 >char ** と const char ** が互換じゃない strcpyのプロトタイプのような場合は分かりますが、もっと深いところでの違いがあるのでしたら、ググりまくります。CとC++ でもconstの意味がいろいろ異なるので油断はできないし。 >Unix だと そういう使い方もあるのですね。
> どういう場合に変更することに意味があるのか詳しく知りたいです。 argcの値も書き換えてよいのです。 解析の終わったオプションをargvから削除して、その数だけargcを減算していけば、引数解析するとき必ずしもループ処理する必要がなくなります。
お礼
どうもご指摘ありがとうございました。
補足
確か、K&Rでのstrcpy を自作する例でも、思いっきり仮引数を変更してたと思います。そこら辺の影響か?とも思ったのですが。 効率より信頼性が重視される現在は、仮引数を変更するのは好ましくない、とも聞きました。 int i; i=argc; とかになるのでしょうか。(ただ、前の回答者様の言うように、組み込み系など余計なメモリが使えない場合は別でしょうが。) これは、別質問にするべきかもしれませんが、組み込み系などは仕方ないにしても、自分の作ったプログラムではmainの引数の変更はしないぞ、とconstを付けることはいけないことなのでしょうか? この方が、コンパイラがチェックしてくれるのでコメントに書くより有用だと思うのですが。たまたま自分の環境では問題なく動いたようですが、禁止事項なのでしょうか?constが型ではなく型修飾子というのが引っかかります。プロトタイプと関数定義で、constのつけ方が違う場合はどうなるのか?とか疑問になってきました。
- ok-kaneto
- ベストアンサー率39% (1798/4531)
ちなみにUnix関連でargv[0]を書き換えてプロセス名を変更するってのはあるみたいですよ。
お礼
大事な話、ありがとうございました。argvは情報の入力だけでなくシェルに情報を知らせる場合もあり得るわけですね。
補足
なるほど、そういう場合もあるから、変更可能になっているのかもしれませんね。情報ありがとうございました。
- ok-kaneto
- ベストアンサー率39% (1798/4531)
>argvの指す文字列を変更する、というのはいくら何でもまずいので、 これが単なる思い込みでしょ。 http://kikakurui.com/x3/X3010-2003-01.html 規格では逆に >仮引数 argc,argv 及び argv 配列が指す文字列は,プログラムによって変更可能でなければならない。 ですよ。変更してはいけない理由はないようです。 ちなみに充分なスタックは用意されているようです。
お礼
私の間違った思い込みを指摘していただいて、ありがとうございました。
補足
規格上、argvは変更可能ということが明示されているわけですね? それなら一応分かります。変更してはいけない理由はない、というより、変更することに意味がある場合というのがあるわけですね? できれば、どういう場合に変更することに意味があるのか詳しく知りたいです。
- yaemon_2006
- ベストアンサー率22% (50/220)
>argv[1] の内容を書き換えてバッファオーバーフロなどなどが起こったら >何が起こるか分からないですし。 それを言うなら、すべての配列をconstにしなければならない。
お礼
ご指摘ありがとうございました。 >それを言うなら、すべての配列をconstにしなければならない。 あるいは、書き換え可能文字列の配列を作るときに、strncpyなどなどを使えるように、文字列幅の情報が得られるようにする、などもありますが、argvでは使えなさそうですね。 バッファオーバーランなどの問題に関しては、自力で調べてみて、どうしても分からなければ、別質問とさせていただきます。
補足
バッファオーバーフローに言及したのは私のミスです。話が発散してしまいますから。取り合えず、オーバーフローしない場合でも、パラメータを書き換えるメリットがあるのか?を知りたいです。また、デメリットもあり得るから疑問だったし。 プログラム名やパラメータがメモリ内のどこに収容されているかは、OSなどに依存するでしょうから、下手に書き換えると動作がおかしくなる可能性があるような気がしまして。また、argvを書き換えるプログラムを書いたとき、後になって、やはり、元のパラメータが何だったか知りたくなると、プログラムの書き換えが大変だし
- 1
- 2
お礼
詳しい説明、ありがとうございました。 argvを変更する重要な場面がよく分かりました。 どちらにしろ、argvの書き換えは慎重にやらなければいけないでしょうが。 「この場合はどうなるんだろう?」「あの場合は?」などの考えが出てきていますが、それらが自己解決できなかったときは別質問とさせていただきます。
補足
あ、なるほど! >昔、プログラムで使えるメモリが貴重だった頃は、どうやってメモリを節約するかが 重要でした。 これは分かるので、そういう歴史的理由かもなどとも思いましたが、組み込み系だとメモリは今でも重要ですね。(組み込み系のプログラムは作ったことがないのでうっかりしていました。) かなり納得したので、質問締め切ることも考えましたが、「ちょっと待った!」という回答が来るかもしれないので、もう少し締め切りはのばしておきます。