• ベストアンサー

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を付けない形が出回っているのでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
回答No.6

> できれば、どういう場合に変更することに意味があるのか詳しく知りたいです。 昔、プログラムで使えるメモリが貴重だった頃は、どうやってメモリを節約するかが 重要でした。そういう世界では、入力パラメータを解析するのにメモリを消費しないで 解析するのがよいコーディングでした。 カンマ区切りのパラメータを解析する時は、strtok()を使って 入力パラメータ(argv)を分解する(区切り文字を\0で書き換えてパラメータを取り出す) というのが普通の方法でした。 strtokのマニュアルでも、サンプルソースにargvで入力されたパラメータを strtokで分解する例が書かれています。 http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/strtok.3.html 今でも組み込み系でメモリがあまり使えないところでは、こういう手法は よく使われると思います。 パラメータを分解するのに、わざわざ新しいメモリ領域を確保して別の領域に 取り出すなんて、(環境によっては)メモリの無駄遣いということです。 このために、C言語の規格でargc,argvは書き換え可能でなければならない ことが明記されています。

mikamikadon
質問者

お礼

詳しい説明、ありがとうございました。 argvを変更する重要な場面がよく分かりました。 どちらにしろ、argvの書き換えは慎重にやらなければいけないでしょうが。 「この場合はどうなるんだろう?」「あの場合は?」などの考えが出てきていますが、それらが自己解決できなかったときは別質問とさせていただきます。

mikamikadon
質問者

補足

あ、なるほど! >昔、プログラムで使えるメモリが貴重だった頃は、どうやってメモリを節約するかが 重要でした。 これは分かるので、そういう歴史的理由かもなどとも思いましたが、組み込み系だとメモリは今でも重要ですね。(組み込み系のプログラムは作ったことがないのでうっかりしていました。) かなり納得したので、質問締め切ることも考えましたが、「ちょっと待った!」という回答が来るかもしれないので、もう少し締め切りはのばしておきます。

その他の回答 (12)

回答No.13

No.6,12です。 > うっかり if( argv[i][0] = '-' )~ と書いてしまう類のミス > コンパイラはスルーする、ということを肝に銘じて、自己責任で気をつける、というのが正解のようですね。 そういうのは、lintチェック等で誤りやすいパターンを警告する工程を ビルドプロセスの中に組み込んで、毎回自動実施するのが正解だと思います。

mikamikadon
質問者

お礼

わざわざ、ありがとうございました。しつこい補足にも関わらず詳しく教えてくださった皆様に感謝いたします。

mikamikadon
質問者

補足

>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 などを外すキャストと言われますが、それだけではないのですね。 大分、事情が分かってきたので、もう少ししたら締め切らせていただきます。

回答No.12

No.6です。 main関数のパラメータにconst属性をつけるのに実質的な危険はないの かもしれませんが、一般論として、もともとconstでないものを自分が参照 しないからといってconst属性をつけた変数で処理するのは、むしろ危険 だと私は思います。 いくらconst属性をつけても、元々の領域は書き換え可能領域です。 自分が参照しかしないからと言ってconst属性をつけて処理しても、 もしかしたら自分のプログラムから呼び出した別のプログラムが その書き換え可能領域を正しく書き換えているかもしれません。 そうすると、勝手にconst属性をつけて処理しているプログラムは もしかすると最適化により、書き換えられたことを認識せず 処理してしまうかもしれません。 const属性というのは、「自分が書き換えませんよ」と宣言している ものではなく、「誰も書き換えない領域です」と宣言しているものだと 私は認識しています。

mikamikadon
質問者

お礼

説明ありがとうございました。 >「誰も書き換えない領域です」と宣言しているものだ mainを呼ぶ、と書いてしまったのは、手が滑ってしまったミスです。すみません。他の関数がmainを呼んだからといって、プログラム名や引数を取得できるわけではありませんから。 constを付けるのが安全であり得るのは、少なくとも、mainのargvをたどる以外にコマンドライン情報のアクセスは絶対にできない場合(そこでガードできる場合)に限りますから、そんな保障はどこにもない状態では確かに危険ですね。 ありがとうございます。

mikamikadon
質問者

補足

プロトタイプでconst付きの自作mainを作って、本来のmainのargvは外から参照できないようにすれば、自作mainの中でargvを弄る関数を呼び出すと警告(or エラー)が出るか?と思ったのですが、最適化とか考えると確かに危険ですね。というか、Cではmainを他の関数から呼ぶことができるので、ここで駄目ですね。 ちなみに、C++風キャストを試してみたら、 const_cast でないと駄目のようですから(2重ポインタの危険が大きい?)やはり、危険なキャストなんですね。 キャストなしの場合、Cだと警告は出てもコンパイルは可能でしたが、C++ではコンパイルエラーですね。 まあ、例えば、 if( argv[i][0] == '-' )~ を、うっかり if( argv[i][0] = '-' )~ と書いてしまう類のミス(プロの方々は知りませんが私はよくやります)は、コンパイラはスルーする、ということを肝に銘じて、自己責任で気をつける、というのが正解のようですね。

noname#208507
noname#208507
回答No.11

> この場合、余計なコマンドライン引数を取り除いた形の文字列配列を > (内容をargv内からコピーして)別に作る方法だと、支障が出るのでしょうか? 支障はないですが、私は理想論だと思います。 小さいコマンドラインプログラムをいくつもいくつも作るとき、main文に2次元配列のメモリ管理をコーディングするのは(特にエラー終了の後始末が)大変ですし、メモリの解放は忘れやすいので、逆に危険ではないでしょうか。コマンドライン引数は、OSの方でメモリ管理してくれるのですから。

mikamikadon
質問者

お礼

ご説明、ありがとうございました。 参考書での独学では、どうしてもサンプルに「エラー処理は煩雑なので省略」したコードが多いので、エラー処理に関しては自己流になってしまうため、さらに危険ですね。

mikamikadon
質問者

補足

>小さいコマンドラインプログラムをいくつもいくつも作るとき >特にエラー終了の後始末 >特にエラー終了の後始末 納得しました。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.10

あ, 思い出した. 引数を解析するライブラリのうち, GNU の getopt は引数の順序を入れ替えてる (そうやって「オプション」を前の方に集め, 「オプションでない引数」を後ろの方にまとめてる). 以下余談: const char const * const * argv は最初の 2つの const が重複してる. ひょっとして char const * const * const argv としたかった? あと, 実は char **p; const char **q; とあると p=q; と q=p; のどっちも代入できないというわな.

mikamikadon
質問者

お礼

情報ありがとうございました。 コマンド引数を解析する関数がarggvの内容を変更しているのであれば、constを付ける意味はないですね。

mikamikadon
質問者

補足

>ひょっとして >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[] のようですね。

noname#208507
noname#208507
回答No.9

No.7です。 > 仮効率より信頼性が重視される現在は、引数を変更するのは好ましくない、 おっしゃる通りだと思います。私もargcやargvを除いて、まず仮引数を書き換えません。 > int i; i=argc; とかになるのでしょうか。 GLUT(OpenGLのツールキット)のような、独自のコマンドライン解析を行うようなライブラリを用いる場合、余計なコマンドライン引数をあらかじめ取り除いてからライブラリに引き渡す方が安全な場合もあるでしょう。 何を安全とするかの方針次第にもよりますが。 > constを付けることはいけないことなのでしょうか? No.8さんの回答の通りかと。

mikamikadon
質問者

お礼

いろいろな場合を考えてくださり、ありがとうございました。 勉強になります。

mikamikadon
質問者

補足

>余計なコマンドライン引数をあらかじめ取り除いてからライブラリに引き渡す方が安全な場合もあるでしょう。 この場合、余計なコマンドライン引数を取り除いた形の文字列配列を(内容をargv内からコピーして)別に作る方法だと、支障が出るのでしょうか?

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.8

#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 とか, そうじゃなかったかなぁ.

mikamikadon
質問者

お礼

丁寧な説明、ありがとうございました。 argv[0]を書き換えて、シェルに情報を伝える、ということは、argvはコマンドライン情報の参照渡し(をポインタを使ってシミュレートしたもの)と考えた方が自然かもしれませんね。

mikamikadon
質問者

補足

gccでオプションなしでやった場合は、const char const * const * argv というところまでやっても警告も何も出なかったのですが、VC++では警告が出ましたので、原則として、やはり仕様としてはやってはいけないことなんだな、と納得しました。移植性が犠牲になっては元も子もないですから。 >char ** と const char ** が互換じゃない strcpyのプロトタイプのような場合は分かりますが、もっと深いところでの違いがあるのでしたら、ググりまくります。CとC++ でもconstの意味がいろいろ異なるので油断はできないし。 >Unix だと そういう使い方もあるのですね。

noname#208507
noname#208507
回答No.7

> どういう場合に変更することに意味があるのか詳しく知りたいです。 argcの値も書き換えてよいのです。 解析の終わったオプションをargvから削除して、その数だけargcを減算していけば、引数解析するとき必ずしもループ処理する必要がなくなります。

mikamikadon
質問者

お礼

どうもご指摘ありがとうございました。

mikamikadon
質問者

補足

確か、K&Rでのstrcpy を自作する例でも、思いっきり仮引数を変更してたと思います。そこら辺の影響か?とも思ったのですが。 効率より信頼性が重視される現在は、仮引数を変更するのは好ましくない、とも聞きました。 int i; i=argc; とかになるのでしょうか。(ただ、前の回答者様の言うように、組み込み系など余計なメモリが使えない場合は別でしょうが。) これは、別質問にするべきかもしれませんが、組み込み系などは仕方ないにしても、自分の作ったプログラムではmainの引数の変更はしないぞ、とconstを付けることはいけないことなのでしょうか? この方が、コンパイラがチェックしてくれるのでコメントに書くより有用だと思うのですが。たまたま自分の環境では問題なく動いたようですが、禁止事項なのでしょうか?constが型ではなく型修飾子というのが引っかかります。プロトタイプと関数定義で、constのつけ方が違う場合はどうなるのか?とか疑問になってきました。

  • ok-kaneto
  • ベストアンサー率39% (1798/4531)
回答No.5

ちなみにUnix関連でargv[0]を書き換えてプロセス名を変更するってのはあるみたいですよ。

mikamikadon
質問者

お礼

大事な話、ありがとうございました。argvは情報の入力だけでなくシェルに情報を知らせる場合もあり得るわけですね。

mikamikadon
質問者

補足

なるほど、そういう場合もあるから、変更可能になっているのかもしれませんね。情報ありがとうございました。

  • ok-kaneto
  • ベストアンサー率39% (1798/4531)
回答No.4

>argvの指す文字列を変更する、というのはいくら何でもまずいので、 これが単なる思い込みでしょ。 http://kikakurui.com/x3/X3010-2003-01.html 規格では逆に >仮引数 argc,argv 及び argv 配列が指す文字列は,プログラムによって変更可能でなければならない。 ですよ。変更してはいけない理由はないようです。 ちなみに充分なスタックは用意されているようです。

mikamikadon
質問者

お礼

私の間違った思い込みを指摘していただいて、ありがとうございました。

mikamikadon
質問者

補足

規格上、argvは変更可能ということが明示されているわけですね? それなら一応分かります。変更してはいけない理由はない、というより、変更することに意味がある場合というのがあるわけですね? できれば、どういう場合に変更することに意味があるのか詳しく知りたいです。

回答No.3

>argv[1] の内容を書き換えてバッファオーバーフロなどなどが起こったら >何が起こるか分からないですし。 それを言うなら、すべての配列をconstにしなければならない。

mikamikadon
質問者

お礼

ご指摘ありがとうございました。 >それを言うなら、すべての配列をconstにしなければならない。 あるいは、書き換え可能文字列の配列を作るときに、strncpyなどなどを使えるように、文字列幅の情報が得られるようにする、などもありますが、argvでは使えなさそうですね。 バッファオーバーランなどの問題に関しては、自力で調べてみて、どうしても分からなければ、別質問とさせていただきます。

mikamikadon
質問者

補足

バッファオーバーフローに言及したのは私のミスです。話が発散してしまいますから。取り合えず、オーバーフローしない場合でも、パラメータを書き換えるメリットがあるのか?を知りたいです。また、デメリットもあり得るから疑問だったし。 プログラム名やパラメータがメモリ内のどこに収容されているかは、OSなどに依存するでしょうから、下手に書き換えると動作がおかしくなる可能性があるような気がしまして。また、argvを書き換えるプログラムを書いたとき、後になって、やはり、元のパラメータが何だったか知りたくなると、プログラムの書き換えが大変だし

関連するQ&A

  • main関数について

    最近C言語を勉強し始めた者です。 main関数について分からないことがあるので質問致します。main関数は自作関数ですよね。ということは自由に引数を決めていいのですか?つまり int main(int argc , char *argv[]) 以外の引数を決めることは出来るのでしょうか。

  • constについて

    このプログラム(一部分)ではconst指定されたものを変更しているように思うのですが ポインタで操作しているからconst指定されている物を変更できるのでしょうか。 int main( int argc, char** argv ){ const char* filename = "stageData.txt"; if ( argc >= 2 ){ filename = argv[ 1 ]; }

  • main(int argc,char **argv[])の意味を教えて下さい

     今晩は、Cの初心者です宜しくお願いします。  main関数の引数で、int main(int argc,char **argv[])とint main(int argc,char *argv[])と書かれている場合がありますが、 「**argv」と「*argv」の意味の違いはどのようなもので、どのように使い分けるのでしょうか。 また、必ずポインタ型でとるという決まりでしょうか。 宜しくお願いします。

  • Cocoa をコマンドラインから実行して引数を受け取りたい

    C・Objective-C に関して全くの初心者ですが、よろしくお願いします。 Cocoa で作ったアプリをコマンドラインから実行して、渡した引数を NSObject のサブクラスで受け取りたいのですがどうしたらよいでしょうか。 main.m の main 関数で受け取る方法はわかったのですが、サブクラス MyObject.m 内の任意のメソッドに渡す方法がわかりません。 // 引数を受け取って表示する int main(int argc, char *argv[]) {   fprintf(stdout, "%s\n", argv[1]);   return NSApplicationMain(argc, (const char **) argv); } ご教示いただけますと幸いです。

  • main関数のとる引数(argc , *argv[])の意味について

     今日は、Cの初心者です宜しくお願いします。    main関数(main(int argc , *argv[]))のとる引数、argcのCと*argv[]のVはどのような意味の略なのでしょうか、 なにか意味があるようなきがするのですが。  宜しくお願いします。

  • コマンドライン引数 *argv[]はなぜポインタ?

    C言語初心者です。 コマンドライン引数、 int main(int argc, char *argv[]) というのを最近勉強しましたが、引数2番目がポインタになっている理由について、 どなたか教えて下さい。 そういう仕様なんだから、それに従いましょう、ということでしょうか? int main(int argc, char argv[]) では、ダメなのでしょうか? このポインタでの引数渡しについて、 なんらかの納得のいく考え方をご存知の方がいらしたら、教えて下さい。 宜しくお願い致します。

  • 引数の渡し方と受け方(argv)

    メインで引数をもらい、それをパラメタチェック関数に 渡す場合、どのような形で渡せばいいですか? ※メインでargvを変数に格納しないで、 そのままargvを関数に渡す方法が分かりません。 int prm_chk(char *argv[]) { /* 処理省略 */ } int main (int argc, char *argv[]) { /* 処理省略 */ /* 以下のパラメタ(argv)の渡し方 */ if(prm_chk(*argv[]) != 0){ exit(-1); } exit(0); }

  • main関数の事についてお聞きします。

    Windowsのプログラムを書くとき、下のようにキッチリ戻り値や引数を書かなくてはならないのに int WINAPI WinMain( HINSTANCE hInstance, // 現在のインスタンスのハンドル HINSTANCE hPrevInstance, // 以前のインスタンスのハンドル LPSTR lpCmdLine, // コマンドライン int nCmdShow // 表示状態 ); C言語で書かれたプログラムは int main(int argc, char *argv[]) ではなく、 int main(void) や void main(void) または int main() や main() だけのモノなど、 いろいろです。 main関数は、関数なのに戻り値や引数を省略して書ける仕様なのは、なぜなのか? ちょっと疑問に思ったのでこちらにお尋ねしました。

  • コマンドプロンプトを使用してのコマンドライン引数

    winXPのコマンドプロンプトを利用して 参考書「独習C」を使い、C言語の学習をしています。 その本の7.4の項目~main()の引数~で main(int argc,char *avgv[])を利用すると コマンドラインから引数をとることができるとありますが 私の学習環境ではうまくいきません。 具体的には #include <stdio.h> int main(int argc, char *argv[]) { int i; for(i=1; i<argc; i++) printf("%s ", argv[i]); return 0; } というプログラムを実行しても何も表示されません。 どうすればコマンドライン引数を利用できますか?

  • C言語でのコマンドライン引数の内部での処理のされ方

    C言語でint main(int argc, char *argv[])とメイン関数を宣言します。 2番目の引数はC言語の文法的にいうと文字列へのポインタの配列だとおもいますが、一般的な関数でこの引数に値を渡すとすると、以下のように宣言されたポインタ配列を渡すことになるとおもいます。 ・宣言 char *pa[]; ・関数への渡し func(pa); 話が元に戻りますが、main関数でもらう場合は、プログラム外部から与えられた引数は(正確に言うとアドレス)、メモリ上ではC言語で書かれたexeファイルの外から実行時にプログラムファイルのメモリ上にコピーされるのでしょうか? 自分でもうまく表現できないのですが、 ・コマンドプロンプトで引数を与えて実行         ↓ ・プログラムファイルのメモリ上に引数がロードされる ということでいいんでしょうか? 自分でもなんだかうまく表現できないので、お暇な方でよろしいので、気が向いた人、回答ください。 よろしくお願いします。

専門家に質問してみよう