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 …
今でも組み込み系でメモリがあまり使えないところでは、こういう手法は
よく使われると思います。
パラメータを分解するのに、わざわざ新しいメモリ領域を確保して別の領域に
取り出すなんて、(環境によっては)メモリの無駄遣いということです。
このために、C言語の規格でargc,argvは書き換え可能でなければならない
ことが明記されています。
この回答への補足
あ、なるほど!
>昔、プログラムで使えるメモリが貴重だった頃は、どうやってメモリを節約するかが
重要でした。
これは分かるので、そういう歴史的理由かもなどとも思いましたが、組み込み系だとメモリは今でも重要ですね。(組み込み系のプログラムは作ったことがないのでうっかりしていました。)
かなり納得したので、質問締め切ることも考えましたが、「ちょっと待った!」という回答が来るかもしれないので、もう少し締め切りはのばしておきます。
詳しい説明、ありがとうございました。
argvを変更する重要な場面がよく分かりました。
どちらにしろ、argvの書き換えは慎重にやらなければいけないでしょうが。
「この場合はどうなるんだろう?」「あの場合は?」などの考えが出てきていますが、それらが自己解決できなかったときは別質問とさせていただきます。
No.13
- 回答日時:
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 などを外すキャストと言われますが、それだけではないのですね。
大分、事情が分かってきたので、もう少ししたら締め切らせていただきます。
No.12
- 回答日時:
No.6です。
main関数のパラメータにconst属性をつけるのに実質的な危険はないの
かもしれませんが、一般論として、もともとconstでないものを自分が参照
しないからといってconst属性をつけた変数で処理するのは、むしろ危険
だと私は思います。
いくらconst属性をつけても、元々の領域は書き換え可能領域です。
自分が参照しかしないからと言ってconst属性をつけて処理しても、
もしかしたら自分のプログラムから呼び出した別のプログラムが
その書き換え可能領域を正しく書き換えているかもしれません。
そうすると、勝手にconst属性をつけて処理しているプログラムは
もしかすると最適化により、書き換えられたことを認識せず
処理してしまうかもしれません。
const属性というのは、「自分が書き換えませんよ」と宣言している
ものではなく、「誰も書き換えない領域です」と宣言しているものだと
私は認識しています。
この回答への補足
プロトタイプでconst付きの自作mainを作って、本来のmainのargvは外から参照できないようにすれば、自作mainの中でargvを弄る関数を呼び出すと警告(or エラー)が出るか?と思ったのですが、最適化とか考えると確かに危険ですね。というか、Cではmainを他の関数から呼ぶことができるので、ここで駄目ですね。
ちなみに、C++風キャストを試してみたら、 const_cast でないと駄目のようですから(2重ポインタの危険が大きい?)やはり、危険なキャストなんですね。
キャストなしの場合、Cだと警告は出てもコンパイルは可能でしたが、C++ではコンパイルエラーですね。
まあ、例えば、 if( argv[i][0] == '-' )~ を、うっかり if( argv[i][0] = '-' )~ と書いてしまう類のミス(プロの方々は知りませんが私はよくやります)は、コンパイラはスルーする、ということを肝に銘じて、自己責任で気をつける、というのが正解のようですね。
説明ありがとうございました。
>「誰も書き換えない領域です」と宣言しているものだ
mainを呼ぶ、と書いてしまったのは、手が滑ってしまったミスです。すみません。他の関数がmainを呼んだからといって、プログラム名や引数を取得できるわけではありませんから。
constを付けるのが安全であり得るのは、少なくとも、mainのargvをたどる以外にコマンドライン情報のアクセスは絶対にできない場合(そこでガードできる場合)に限りますから、そんな保障はどこにもない状態では確かに危険ですね。
ありがとうございます。
No.11
- 回答日時:
> この場合、余計なコマンドライン引数を取り除いた形の文字列配列を
> (内容をargv内からコピーして)別に作る方法だと、支障が出るのでしょうか?
支障はないですが、私は理想論だと思います。
小さいコマンドラインプログラムをいくつもいくつも作るとき、main文に2次元配列のメモリ管理をコーディングするのは(特にエラー終了の後始末が)大変ですし、メモリの解放は忘れやすいので、逆に危険ではないでしょうか。コマンドライン引数は、OSの方でメモリ管理してくれるのですから。
ご説明、ありがとうございました。
参考書での独学では、どうしてもサンプルに「エラー処理は煩雑なので省略」したコードが多いので、エラー処理に関しては自己流になってしまうため、さらに危険ですね。
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; のどっちも代入できないというわな.
この回答への補足
>ひょっとして
>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[] のようですね。
情報ありがとうございました。
コマンド引数を解析する関数がarggvの内容を変更しているのであれば、constを付ける意味はないですね。
No.9
- 回答日時:
No.7です。
> 仮効率より信頼性が重視される現在は、引数を変更するのは好ましくない、
おっしゃる通りだと思います。私もargcやargvを除いて、まず仮引数を書き換えません。
> int i; i=argc; とかになるのでしょうか。
GLUT(OpenGLのツールキット)のような、独自のコマンドライン解析を行うようなライブラリを用いる場合、余計なコマンドライン引数をあらかじめ取り除いてからライブラリに引き渡す方が安全な場合もあるでしょう。
何を安全とするかの方針次第にもよりますが。
> constを付けることはいけないことなのでしょうか?
No.8さんの回答の通りかと。
この回答への補足
>余計なコマンドライン引数をあらかじめ取り除いてからライブラリに引き渡す方が安全な場合もあるでしょう。
この場合、余計なコマンドライン引数を取り除いた形の文字列配列を(内容をargv内からコピーして)別に作る方法だと、支障が出るのでしょうか?
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 とか, そうじゃなかったかなぁ.
この回答への補足
gccでオプションなしでやった場合は、const char const * const * argv というところまでやっても警告も何も出なかったのですが、VC++では警告が出ましたので、原則として、やはり仕様としてはやってはいけないことなんだな、と納得しました。移植性が犠牲になっては元も子もないですから。
>char ** と const char ** が互換じゃない
strcpyのプロトタイプのような場合は分かりますが、もっと深いところでの違いがあるのでしたら、ググりまくります。CとC++ でもconstの意味がいろいろ異なるので油断はできないし。
>Unix だと
そういう使い方もあるのですね。
丁寧な説明、ありがとうございました。
argv[0]を書き換えて、シェルに情報を伝える、ということは、argvはコマンドライン情報の参照渡し(をポインタを使ってシミュレートしたもの)と考えた方が自然かもしれませんね。
No.7
- 回答日時:
> どういう場合に変更することに意味があるのか詳しく知りたいです。
argcの値も書き換えてよいのです。
解析の終わったオプションをargvから削除して、その数だけargcを減算していけば、引数解析するとき必ずしもループ処理する必要がなくなります。
この回答への補足
確か、K&Rでのstrcpy を自作する例でも、思いっきり仮引数を変更してたと思います。そこら辺の影響か?とも思ったのですが。
効率より信頼性が重視される現在は、仮引数を変更するのは好ましくない、とも聞きました。
int i; i=argc; とかになるのでしょうか。(ただ、前の回答者様の言うように、組み込み系など余計なメモリが使えない場合は別でしょうが。)
これは、別質問にするべきかもしれませんが、組み込み系などは仕方ないにしても、自分の作ったプログラムではmainの引数の変更はしないぞ、とconstを付けることはいけないことなのでしょうか? この方が、コンパイラがチェックしてくれるのでコメントに書くより有用だと思うのですが。たまたま自分の環境では問題なく動いたようですが、禁止事項なのでしょうか?constが型ではなく型修飾子というのが引っかかります。プロトタイプと関数定義で、constのつけ方が違う場合はどうなるのか?とか疑問になってきました。
No.4
- 回答日時:
>argvの指す文字列を変更する、というのはいくら何でもまずいので、
これが単なる思い込みでしょ。
http://kikakurui.com/x3/X3010-2003-01.html
規格では逆に
>仮引数 argc,argv 及び argv 配列が指す文字列は,プログラムによって変更可能でなければならない。
ですよ。変更してはいけない理由はないようです。
ちなみに充分なスタックは用意されているようです。
この回答への補足
規格上、argvは変更可能ということが明示されているわけですね? それなら一応分かります。変更してはいけない理由はない、というより、変更することに意味がある場合というのがあるわけですね? できれば、どういう場合に変更することに意味があるのか詳しく知りたいです。
補足日時:2014/09/14 08:59お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
関連するカテゴリからQ&Aを探す
おすすめ情報
- ・漫画をレンタルでお得に読める!
- ・一回も披露したことのない豆知識
- ・これ何て呼びますか
- ・チョコミントアイス
- ・初めて自分の家と他人の家が違う、と意識した時
- ・「これはヤバかったな」という遅刻エピソード
- ・これ何て呼びますか Part2
- ・許せない心理テスト
- ・この人頭いいなと思ったエピソード
- ・牛、豚、鶏、どれか一つ食べられなくなるとしたら?
- ・あなたの習慣について教えてください!!
- ・ハマっている「お菓子」を教えて!
- ・高校三年生の合唱祭で何を歌いましたか?
- ・【大喜利】【投稿~11/1】 存在しそうで存在しないモノマネ芸人の名前を教えてください
- ・好きなおでんの具材ドラフト会議しましょう
- ・餃子を食べるとき、何をつけますか?
- ・あなたの「必」の書き順を教えてください
- ・ギリギリ行けるお一人様のライン
- ・10代と話して驚いたこと
- ・家の中でのこだわりスペースはどこですか?
- ・つい集めてしまうものはなんですか?
- ・自分のセンスや笑いの好みに影響を受けた作品を教えて
- ・【お題】引っかけ問題(締め切り10月27日(日)23時)
- ・大人になっても苦手な食べ物、ありますか?
- ・14歳の自分に衝撃の事実を告げてください
- ・架空の映画のネタバレレビュー
- ・「お昼の放送」の思い出
- ・昨日見た夢を教えて下さい
- ・ちょっと先の未来クイズ第4問
- ・【大喜利】【投稿~10/21(月)】買ったばかりの自転車を分解してひと言
- ・メモのコツを教えてください!
- ・CDの保有枚数を教えてください
- ・ホテルを選ぶとき、これだけは譲れない条件TOP3は?
- ・家・車以外で、人生で一番奮発した買い物
- ・人生最悪の忘れ物
- ・【コナン30周年】嘘でしょ!?と思った○○周年を教えて【ハルヒ20周年】
- ・10秒目をつむったら…
- ・人生のプチ美学を教えてください!!
- ・あなたの習慣について教えてください!!
- ・都道府県穴埋めゲーム
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
テキストデータをそのままバイ...
-
fgetsなどのときのstdinのバッ...
-
switch文で文字を比較すること...
-
絶対パスからのファイル名の切...
-
charでの計算?
-
配列をnビットシフトする
-
文字列から空白を取り除きたい...
-
C言語です
-
atoi( ) の反対をやりたい
-
C言語のポインターで詰まっている
-
間接参照のレベルが異なっています
-
main の引数には const 付けた方が
-
型変換
-
CStringをwchar_tに変換したい
-
ncursesで...
-
YUV⇔RGB変換がうまくいきません。
-
C言語で、入力された、文字列を...
-
C言語の入力した文字を反転させ...
-
英単語をアルファベット順に
-
strncpyと_tcsncpy_sのヌルの扱...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
charでの計算?
-
文字列から空白を取り除きたい...
-
CStringをwchar_tに変換したい
-
C言語のfor文です。 繰り返しの...
-
charからLPTSTRへの変換方法
-
fgetsなどのときのstdinのバッ...
-
'const char *' 型は 'char *' ...
-
間接参照のレベルが異なっています
-
double型の値をchar配列に変換...
-
atoi( ) の反対をやりたい
-
間接操作のレベルとは
-
ネットワークにつながっている...
-
型変換
-
テキストデータをそのままバイ...
-
文字列ポインタを結合
-
3桁区切(コンマ)記号をつけ...
-
C言語です
-
Win32APIでのエディットボック...
-
TCHAR文字列?の特定部分の数字...
-
絶対パスからのファイル名の切...
おすすめ情報