C は関数から主な引数を取得します。 メイン関数の引数。 コマンドライン引数の使用


プログラムを開始するときに、プログラムに何らかの情報を渡すと便利な場合があります。 通常、この情報はコマンドライン引数を介して main() に渡されます。 コマンドライン引数は、オペレーティング システムのコマンド ラインでプログラム名に続いて入力される情報です。 たとえば、プログラムのコンパイルを開始するには、コマンド ラインのプロンプトの後に次のような内容を入力する必要があります。

Cc プログラム名

プログラム名はコマンド ライン引数で、コンパイルしようとしているプログラムの名前を指定します。

コマンド ライン引数を受け入れるには、argc と argv という 2 つの特別な組み込み引数が使用されます。 argc パラメータにはコマンド ラインの引数の数が含まれ、整数です。最初の引数はプログラムの名前であるため、常に 1 以上になります。 argv パラメータは、文字列へのポインタの配列へのポインタです。 この配列では、各要素がコマンド ライン引数を指します。 すべてのコマンド ライン引数は文字列であるため、プログラムの開発時に数値を目的のバイナリ形式に変換する必要があります。

コマンドライン引数を使用する簡単な例を次に示します。 「Hello」という単語とあなたの名前が画面に表示されます。これはコマンド ライン引数として指定する必要があります。

#含む #含む int main(int argc, char *argv) ( if(argc!=2) ( printf("名前を入力するのを忘れました。\n"); exit(1); ) printf("Hello %s", argv) ; 0 を返します; )

このプログラム名 (name) を呼び出し、名前が Tom である場合、プログラムを実行するには、コマンド ラインに名前 Tom を入力する必要があります。 プログラムを実行すると、「こんにちは、トム」というメッセージが画面に表示されます。

多くの環境では、すべてのコマンド ライン引数をスペースまたはタブで区切る必要があります。 カンマ、セミコロン、および類似の文字は区切り文字とみなされません。 例えば、

スポットを走って、走って

は 3 つの文字列で構成されますが、

エリック、リック、フレッド

は単一の文字列を表します。通常、カンマは区切り文字とみなされません。

文字列にスペースが含まれている場合、環境によっては文字列を二重引用符で囲むと、複数の引数が生成されないようにすることができます。 その結果、文字列全体が 1 つの引数とみなされます。 オペレーティング システムがコマンド ライン パラメータを設定する方法の詳細については、オペレーティング システムのドキュメントを参照してください。

argv を正しく宣言することが非常に重要です。 最もよく行われる方法は次のとおりです。

Char *argv;

空の角括弧は、配列の長さが無限であることを示します。 argv 配列にインデックスを付けることで、個々の引数にアクセスできるようになりました。 たとえば、argv は最初の文字列を指します。これは常にプログラム名です。 argv は最初の引数を指します。以下同様です。

コマンド ライン引数を使用するもう 1 つの小さな例は、次のプログラム countdown です。 このプログラムは、ある値 (コマンド ラインで指定された) から開始して逆方向にカウントし、0 に達するとビープ音を鳴らします。開始値を含む最初の引数は、標準の atoi 関数 () を使用して整数値に変換されることに注意してください。 コマンドラインの 2 番目の引数 (プログラム名を引数と見なす場合は 3 番目) が「display」(表示) 行の場合、カウントの結果が (逆の順序で) 画面に表示されます。

/* 逆順にカウントするプログラム。 */ #含む #含む #含む #含む int main(int argc, char *argv) ( int disp, count; if(argc<2) { printf("В командной строке необходимо ввести число, с которого\n"); printf("начинается отсчет. Попробуйте снова.\n"); exit(1); } if(argc==3 && !strcmp(argv, "display")) disp = 1; else disp = 0; for(count=atoi(argv); count; --count) if(disp) printf("%d\n", count); putchar("\a"); /* здесь подается звуковой сигнал */ printf("Счет закончен"); return 0; }

コマンドライン引数が指定されていない場合は、エラーメッセージが表示されますのでご注意ください。 コマンド ライン引数を受け取るプログラムは、多くの場合、次のことを行います。 ユーザーが必要な情報を入力せずにこれらのプログラムを実行すると、引数を正しく指定する方法に関する指示が表示されます。

コマンド ライン引数の 1 つの個々の文字にアクセスするには、argv に 2 番目のインデックスを入力します。 たとえば、次のプログラムは、呼び出しに使用されたすべての引数を 1 文字ずつ出力します。

#含む int main(int argc, char *argv) ( int t, i; for(t=0; t

argv の最初のインデックスは文字列へのアクセスを提供し、2 番目のインデックスはその文字列の個々の文字へのアクセスを提供することに注意してください。

通常、argc と argv は、プログラムの開始時に必要となる初期コマンドをプログラムに与えるために使用されます。 たとえば、コマンド ライン引数では、ファイル名、オプション、代替動作などの情報を指定することがよくあります。 コマンド ライン引数を使用すると、プログラムに「プロフェッショナルな外観」が与えられ、バッチ ファイルでの使用が容易になります。

argc および argv という名前は伝統的なものですが、必須ではありません。 これら 2 つのパラメータは main() 関数内で必要に応じて呼び出すことができます。 さらに、一部のコンパイラは main() の追加の引数をサポートしている場合があるため、コンパイラのドキュメントを必ず確認してください。

プログラムがコマンド ライン パラメーターを必要としない場合、main() 関数をパラメーターなしとして明示的に宣言するのが最も一般的です。 この場合、この関数のパラメータ リストにはキーワード void が使用されます。

タグ: コマンドラインオプション

コマンドラインオプション

C はコンパイル言語です。 アセンブル後のプログラムは実行可能ファイルになります (動的ライブラリやドライバーなどの作成は考慮していません)。 私たちのプログラムは非常にシンプルで、ランタイム ライブラリが含まれていないため、同じオペレーティング システム (および同様のアーキテクチャ) を備えたコンピュータに転送して、そこで実行できます。

プログラムは起動時にパラメータを受け入れることができます。 これらは main 関数の引数です。 main関数の全体像は次のとおりです

Void main(int argc, char **argv) ( ... )

argc の最初の引数は、関数に渡されるパラメータの数です。 2 番目の引数は文字列の配列、つまりパラメータそのものです。 関数のパラメータは何でもよいため、文字列として渡され、プログラム自体がパラメータを解析して目的の型に変換する必要があります。

最初の引数 (argv) は常にプログラム名です。 この場合、プログラムがどこから起動されたかに応じて名前が表示されます。

#含む #含む void main(int argc, char **argv) ( printf("%s", argv); )

次に、コマンドラインを少し操作する方法を学びましょう。 これはプログラムに引数を渡すために必要になります。 Win+R キーの組み合わせにより、[ファイル名を指定して実行] ウィンドウが表示されます。 「cmd」と入力すると、コマンドラインが開きます。 [スタート] メニューを検索して cmd.exe を見つけることもできます。 Unix のようなオペレーティング システムでは、ターミナル プログラムを呼び出すことができます。

あまり多くのコマンドを学習する必要はありません。 仕事に必要なものだけ。

cd コマンドはすべてのオペレーティング システムに標準であり、目的のフォルダーに移動します。 - という 2 つの予約名があります。 (ドット) と.. (2 つのドット)。 ドットは現在のフォルダーの名前です。

どこにも行かない

親フォルダーにアクセスする

親フォルダーに移動

目的の場所に移動するには、CD アドレスを書き込みます。 たとえば、Windows のフォルダー C:\Windows\System32 に移動する必要があります。

Cd C:\Windows\System32

Linux で /var/mysql フォルダーに移動する必要がある場合

Cd /var/mysql

パスにスペースが含まれる場合は、二重引用符で囲まれます。

Cd「D:\ドキュメントと設定\プロローグ」

ターミナルには次の便利な機能があります。上矢印を押すと、前に実行したコマンドが表示されます。 Tab キーを押すと、ターミナルは既知のコマンドへの行を完成させようとするか、現在のフォルダー内のすべてのフォルダーとファイルをたどってパスを完成させようとします。
「cd C:\」と入力します。
Tab キーを押して、何が起こるかを確認してください。

もう 1 つの重要なコマンドである Windows では dir、Linux では ls は、現在のフォルダー (現在いるフォルダー) の内容をコンソールに表示します。

プログラムが完全な名前を返しました。 プログラムが置かれているフォルダーに移動し、その内容を確認します。


フォルダーに移動したので、プログラムを実行できます。 これを行うには、彼女の名前を入力します。


名前が変更されていることに注意してください。 プログラムは独自のフォルダーから呼び出されるため、相対名が表示されます。 次に、プログラムを変更して、すべての引数を出力するようにしましょう。 彼女に与えられたもの。

#含む #含む void main(int argc, char **argv) ( int i; for (i = 0; i< argc; i++) { printf("%s\n", argv[i]); } }

プロジェクトを組み立てます。 アセンブルする前に、プログラムが閉じられていることを確認してください。 次に、プログラムを呼び出して、さまざまな引数を渡します。 これを行うには、プログラムの名前と引数をスペースで区切って記述します。


次に、2 つの数値引数を受け取り、それらの合計を出力するプログラムを書いてみましょう。

#含む #含む #含む void main(int argc, char **argv) ( int a, b; if (argc != 3) ( printf("エラー: %d 個の引数が見つかりました。正確に 2 つ必要です", argc-1); exit(1); ) a = atoi(argv); b = atoi(argv); printf("%d", a + b); )

集まって電話しましょう


これがほとんどのプログラムの動作方法です。 ショートカットをクリックすると、そのショートカットが参照しているプログラムが呼び出されます。 ほとんどのプログラムはさまざまな引数も受け入れます。 たとえば、コマンド ラインから Firefox ブラウザを呼び出し、引数を渡すことができます。
firefox.exe "www.mozilla.org" "site" を実行すると、指定したアドレスにあるサイトが 2 つのタブですぐに開きます。

多くの標準コマンドにもパラメータがあります。 Windows ではスラッシュで始まり、Unix ではマイナス 1 つまたは 2 つで始まるのが通例です。 例えば

フォルダーのみを表示しますが、Linux ターミナルでは

Ls -l は、属性を持つすべてのファイルとフォルダーをリストします。

追加の Windows コマンドを表示するには、コマンド ラインで help と入力するか、マニュアルを参照してください (インターネットで簡単に見つけることができます)。 Linux にはさらに多くのコマンドとそのオプションがあり、その一部は独立したプログラミング言語であるため、少なくとも最小限のセットとそのオプションを学習する価値があります。

Borland C++ は、main() に対する 3 つの引数をサポートしています。 最初の 2 つは従来の argc と argv です。 これらは、ANSI C 標準で定義されている main() の唯一の引数であり、コマンド ライン引数をプログラムに渡すことができます。 コマンド ライン引数は、オペレーティング システムのコマンド ライン上のプログラム名に続く情報です。 たとえば、プログラムが Borland ライン コンパイラを使用してコンパイルされる場合、通常は bcc と入力されます。 プログラム名

どこ プログラム名コンパイルが必要なプログラムです。 プログラム名は引数としてコンパイラに渡されます。

argc パラメータにはコマンド ライン引数の数が含まれており、整数です。 プログラム名が最初の引数として適格であるため、これは常に少なくとも 1 に等しくなります。 argv パラメータは、文字ポインタの配列へのポインタです。 この配列の各要素はコマンド ライン引数を指します。 すべてのコマンドライン引数は文字列です。 すべての数値はプログラムによって内部形式に変換されます。 次のプログラムは、プログラム名の直後にユーザー名を入力すると、「Hello」の後にユーザー名を出力します。

#含む

{
if(argc!=2)
{
printf("名前を入力するのを忘れました\n");
1 を返します。
}
printf("こんにちは %s", argv);
0を返します。
}

このプログラム名をユーザー名 Sergey とすると、プログラムを開始するには次のように入力する必要があります。
名前はセルゲイ。
プログラムの結果として、次のものが表示されます。
「こんにちは、セルゲイ。」

コマンドライン引数はスペースまたはタブで区切る必要があります。 カンマ、セミコロン、および類似の文字は区切り文字とみなされません。 例えば:

3行で構成されていますが、

ハーブ、リック、フレッド

これは 1 行です。カンマは区切り文字ではありません。

スペースまたはタブを含む文字列を単一の引数として渡す必要がある場合は、それを二重引用符で囲む必要があります。 たとえば、これは 1 つの引数です。

"これはテストです"

argv を正しく宣言することが重要です。 最も典型的な方法は次のとおりです。

空の括弧は、配列が固定長ではないことを示します。 argv インデックスを使用して個々の要素にアクセスできます。 たとえば、argv は最初の行を指し、常にプログラム名が含まれます。 argv は次の行を指します。以下同様です。

以下はコマンドライン引数の使用例です。 コマンドラインで指定された値からカウントダウンし、ゼロに達すると信号を発します。 最初の引数には、標準の atoi() 関数を使用して整数に変換された数値が含まれることに注意してください。 文字列「display」が 2 番目の引数として存在する場合、カウンター自体が画面に表示されます。

/* カウントプログラム */

#含む
#含む
#含む
int main(int argc, char *argv)
{
int disp、カウント;
if(argc<2)
{
printf("カウントの長さを入力する必要があります\n");
printf("コマンド ライン上です。もう一度お試しください。\n");
1 を返します。
}
if (argc==3 && !strcmp(argv,"display")) disp = 1;
それ以外の場合は disp = 0;
for(count=atoi(argv); count; -count)
if (disp) printf("%d ", count);
printf("%c", "\a"); /* ほとんどのコンピュータではこれは呼び出しです */
0を返します。
}

引数を指定しない場合はエラーメッセージが表示されますのでご注意ください。 これは、正しい情報なしでプログラムを実行しようとした場合に、コマンド ライン引数を使用して命令を発行するプログラムで最も一般的です。

個々のコマンド ライン文字にアクセスするには、argv に 2 番目のインデックスを追加します。 たとえば、次のプログラムは、呼び出されたすべての引数を一度に 1 文字ずつ出力します。

#含む
int main(int argc, char *argv)
{
int t、i;
for(t=0; t {
i = 0;
while(argv[t][i])
{
printf("%c", argv[t][i]);
}
printf(" ");
}
0を返します。
}

最初のインデックスは文字列にアクセスするためのものであり、2 番目のインデックスは文字列内の文字にアクセスするためのものであることを覚えておく必要があります。

通常、argc と argv はソース コマンドを取得するために使用されます。 理論的には最大 32767 個の引数を指定できますが、ほとんどのオペレーティング システムではそれに近づくことさえできません。 通常、これらの引数はファイル名またはオプションを指定するために使用されます。 コマンド ライン引数を使用すると、プログラムにプロフェッショナルな外観が与えられ、プログラムをバッチ ファイルで使用できるようになります。

Borland C++ に付属の WILDARGS.OBJ ファイルを含めると、*.EXE 型の引数でテンプレートを使用できます。 (Borland C++ は自動的にワイルドカードを処理し、それに応じて argc をインクリメントします。) たとえば、WILDARGS.OBJ を次のプログラムに接続すると、コマンド ラインで指定したファイル名に一致するファイルの数が表示されます。

/* このプログラムを WILDARGS.OBJ にリンクします */

#含む
int main(int argc, char *argv)
{
int i を登録します。
printf("%d ファイルが指定された名前と一致します\n", argc-1);
printf("それらは次のとおりです: ");
for(i=1; i printf("%s ", argv[i]);
0を返します。
}

このプログラムを WA と呼び、次のように実行すると、EXE 拡張子を持つファイルの数とこれらのファイル名のリストが取得されます。

argc と argv に加えて、Borland C++ は 3 番目のコマンド ライン引数 -env も提供します。 env パラメータを使用すると、プログラムはオペレーティング システム環境に関する情報にアクセスできます。 env パラメータは argc および argv の後に続く必要があり、次のように宣言されます。

ご覧のとおり、env は argv と同じ方法で宣言されます。 argv と同様に、これは文字列の配列へのポインタです。 各行は、オペレーティング システムによって定義された環境文字列です。 env パラメータには、環境行の数を示す同等の argc パラメータがありません。 代わりに、環境の最後の行は null です。 次のプログラムは、オペレーティング システムで現在定義されているすべての環境文字列を出力します。

/* このプログラムはすべての環境行を表示します */

#含む
int main(int argc, char *argv, char *env)
{
int t;
for(t=0; env[t]/ t++)
printf("%s\n", env[t]);
0を返します。
}

argc と argv はプログラムでは使用されませんが、パラメーター リストに存在する必要があることに注意してください。 C はパラメータ名を知りません。 代わりに、それらの使用はパラメータが宣言された順序によって決まります。 実際、パラメータには好きな名前を付けることができます。 argc、argv、および env は従来の名前であるため、プログラムを読んだ人がこれらが main() 関数の引数であることをすぐに理解できるように、これらを引き続き使用することが最善です。

プログラムの一般的なタスクは、環境文字列で定義された値を検索することです。 たとえば、PATH 行の内容により、プログラムは検索パスを使用できるようになります。 次のプログラムは、標準の検索パスを宣言する文字列を検索する方法を示しています。 これは、次のプロトタイプを持つ標準ライブラリ関数 strstr() を使用します。

Char *strstr(const char *str1, const char *str2);

strstr() 関数は、str2 が指す文字列内で str1 が指す文字列を検索します。 そのような文字列が見つかった場合は、最初の位置へのポインタが返されます。 一致するものが見つからない場合、関数は NULL を返します。

/* プログラムは環境文字列の中から PATH を含む行を検索します */

#含む
#含む
int main (int argc、char *argv、char *env)
{
int t;
for(t=0; env[t]; t++)
{
if(strstr(env[t], "PATH"))
printf("%s\n", env[t]);
}
0を返します。
}

プログラムが呼び出されたときに、コマンドラインからプログラムにデータが転送されることがあります。 このようなデータはコマンドライン引数と呼ばれます。 たとえば、次のようになります。

./a.out test.txt ls -lt /home/peter/

ここでは、プログラム a.out (現在のディレクトリから) および ls (PATH 環境変数で指定された同じディレクトリから) が呼び出されます。 コマンドラインからの最初のプログラムは 1 つの単語 (test.txt) を受け取り、2 番目のプログラムは 2 つの単語 (-lt と /home/peter/) を受け取ります。

プログラムが C で書かれている場合、プログラムが起動されると、制御はすぐに main() 関数に転送されます。したがって、この関数がパラメータ変数に割り当てられるコマンド ライン引数を受け取ります。

以前は、main() 関数を、パラメーターを受け取らず、何も返さないように定義しました。 実際、C 言語では、デフォルトで (他に何も定義されていない場合) 関数は整数を返します。 これは間違いありません。 このようにコードを書くと:

main() ( printf ("こんにちは \n") ; 0 を返します。 )

そうすれば、コンパイル中に警告やエラーは発生しません。 int main() を書いた場合も同じことが起こります。 これは、関数がデフォルトで何も返さない (void) のではなく、整数を返すことを証明しています。 ただし、関数が返す内容は、たとえば voidmain() や float main() のように常に「オーバーライド」できます。

コマンド ラインからプログラムを呼び出す場合、次のデータのペアが常にプログラムに渡されます。

  1. 整数、呼び出されたときのコマンドライン上のワード数 (スペースで区切られた要素) を示します。
  2. 文字列の配列へのポインタ, ここで、各行はコマンド ラインとは別の単語です。

プログラム名自体も重要であることに注意してください。 たとえば、呼び出しが次のようになったとします。

./a.out 12 テーマ 2

この場合、プログラムの最初の引数は値 4 を持ち、文字列の配列は ("./a.out", "12", "theme", "2") として定義されます。

用語に注意してください。プログラム引数は 2 つ (数値と配列) しかありませんが、コマンド ライン引数はいくつでも指定できます。 コマンドライン引数はプログラム引数 (main() 関数引数) に「変換」されます。
このデータ (数値とポインタ) は、./a.out のように何も渡さずに単に名前で呼び出された場合でも、プログラムに渡されます。 この場合、最初の引数は値 1 を持ち、2 番目の引数は 1 行だけで構成される配列 (「./a.out」) を指します。

データがプログラムに渡されたからといって、main() 関数がそれを受け入れる必要があるわけではありません。 main() 関数がパラメーターなしで定義されている場合、コマンド ライン引数にアクセスすることはできません。 ただし、送信を妨げるものは何もありません。 エラーは発生しません。

プログラムに渡されたデータにアクセスするには、データを変数に割り当てる必要があります。 引数はすぐに main() に渡されるため、そのヘッダーは次のようになります。
main (int n, char *arr)

最初の変数 (n) には単語の数が含まれ、2 番目の変数には文字列の配列へのポインターが含まれます。 多くの場合、2 番目のパラメーターは **arr として記述されます。 しかし、それは同じことです。 文字列の配列自体には、要素として文字列へのポインタが含まれていることを思い出してください。 そして、配列の最初の要素へのポインターを関数に渡します。 ポインタをポインタに渡していることがわかります。つまり、 **編曲

エクササイズ
次のようなプログラムを作成します。

#含む int main(int argc, char ** argv) ( int i; printf ("%d \n"、argc) ; for (i= 0 ; i< argc; i++ ) puts (argv[ i] ) ; }

呼び出されたときにコマンド ライン上の単語数が表示され、各単語が新しい行に表示されます。 コマンドライン引数を使用せずに、または引数を使用して呼び出します。

プログラムでは、パラメーター変数 argc と argv を使用しました。 これらの名前を使用するのが通例ですが、実際には何でも構いません。 あなただけでなく他のプログラマにとってもプログラムがより理解しやすいように、この標準に従うことをお勧めします。

データをプログラムに転送することの実際的な重要性

GNU/Linux コマンド ラインの使用経験がある場合は、ほとんどのコマンドにスイッチと引数があることをご存知でしょう。 たとえば、ディレクトリの内容を表示したり、コピー、移動したりする場合、コマンドが実行されるファイル システム オブジェクトが引数として指定されます。 その実装の機能はキーを使用して決定されます。 たとえば、チーム内で

Cp -r ../les_1 ../les_101

cp はコマンド名、-r はスイッチ、../les_1 および ../les_101 はコマンド引数です。

一般に、ほとんどの場合、プログラム実行プロセスのファイル アドレスと「修飾子」 (これらはキー) が、プログラムの起動時にプログラムに転送されます。

書き込みまたは追加のためにコマンド ラインでユーザーが指定したファイルを開き、プログラムの実行中にユーザーがキーボードから入力したのと同じ情報をそこに書き込む (追加する) プログラムを作成してみましょう。

#含む #含む main (int argc, char ** argv) ( int i, ch; FILE * f[ 5 ] ; if (argc< 3 || argc >7) ((を入れます) 「パラメータの数が無効です」) ; 1 を返します。 ) if (strcmp (argv[ 1 ] , "-w​​" ) != 0 && strcmp (argv[ 1 ] , "-a" ) != 0 ) ( puts ( 「最初のパラメータは -w または -a のいずれかです」) ; 2 を返します。 ) for (i= 0 ; i< argc- 2 ; i++ ) { f[ i] = fopen (argv[ i+ 2 ] , argv[ 1 ] + 1 ) ; if (f[ i] == NULL) { printf (「ファイル %s を開けません\n」、argv[ i+ 2 ] ) ; 3 を返します。 ) ) while ((ch = getchar () ) != EOF) for (i= 0 ; i< argc- 2 ; i++ ) putc (ch, f[ i] ) ; for (i= 0 ; i < argc- 2 ; i++ ) fclose (f[ i] ) ; return 0 ; }

コードの説明:

  1. 5 つのファイル ポインターの配列が作成されます。 したがって、同時に開くことができるファイルは 5 つまでです。 最初のファイルのファイル ポインタは配列要素 f に格納され、2 番目のファイルは f に格納されます。
  2. コマンドライン引数の数がチェックされます。 少なくとも 3 つはあるはずです。なぜなら... 1 つ目はプログラム名、2 つ目はファイルを開くモード、3 つ目は書き込まれる最初のファイルまたは唯一のファイルです。 このプログラムでは開くことができるファイルは 5 つだけであるため、コマンド ライン引数の合計数は 7 つを超えることはできません。 したがって、引数の数が 3 未満または 7 を超える場合、プログラムは終了します。 return ステートメントにより、その後にさらにコードがある場合でも、関数が終了します。 関数から返された値が 0 に等しくない場合、親プロセスはプログラムがエラーで終了したというメッセージとして解釈することができます。
  3. 2 番目のコマンドライン引数の有効性をチェックします。 「-w」でも「-a」でもない場合、2 番目の if の条件式は 1 (true) を返します。 strcmp() 関数を使用すると、文字列を比較でき、それらが等しい場合は 0 を返します。
  4. for ループは、argv 配列の 3 番目の要素で始まる指定されたアドレスにあるファイルを開きます。 これが、argv 配列の要素を 3 番目から取得するために i に 2 が追加される理由です。 argc-2 式は、渡されたファイル名の数を示します。 なぜなら argc は、コマンド ライン引数の合計数を保存します。そのうちの最初の 2 つはファイル名ではありません。
  5. 式 argv+1 を使用すると、文字列「-w」(または「-a」)から部分文字列「w」(または「a」)を「切り取る」ことができます。 argv は本質的には文字列の最初の要素へのポインタです。 ポインターに 1 を追加すると、それが配列の次の要素に移動します。
  6. ファイルを開けない場合、fopen() 関数は NULL を返します。 この場合、プログラムは終了します。
  7. ユーザーがキーボードで入力したすべての文字は、開いているすべてのファイルに書き込まれます。
  8. 最後にファイルは閉じられます。

ある日、私は Linux のプロセスの main 関数のスタックの内容に興味を持ちました。 いくつかの調査を行ったので、その結果を紹介します。

main 関数を記述するためのオプション:
1. int main()
2. int main(int argc, char **argv)
3. int main(int argc, char **argv, char **env)
4. int main(int argc, char **argv, char **env, ElfW(auxv_t) auxv)
5. int main(int argc, char **argv, char **env, char **apple)

Argc - パラメータの数
argv - コマンドラインパラメータ文字列へのポインタのヌル終端配列
env は、環境変数の文字列へのポインタの null 終端配列です。 NAME=VALUE 形式の各行
auxv - 補助値の配列 (PowerPC でのみ使用可能)
apple - 実行可能ファイルへのパス (MacOS および Darwin の場合)
補助ベクトルは、有効なユーザー識別子、setuid ビット属性、メモリ ページ サイズなど、さまざまな追加情報を含む配列です。

スタック セグメントのサイズは、マップ ファイルで確認できます。
猫 /proc/10918/maps

7ffffffa3000-7ffffffff000 rw-p 00000000 00:00 0

ローダーは制御を main に移す前に、コマンド ライン パラメーター、環境変数、および補助ベクトルの配列の内容を初期化します。
初期化後、スタックの最上位は 64 ビット バージョンでは次のようになります。
上には先輩の挨拶。

1. 0x7ffffffff000 スタック セグメントの最上部のポイント。 通話によりセグメンテーション違反が発生する
0x7ffffffff0f8 ヌル 空所* 8 0x00"
2. ファイル名 チャー 1+ 「/tmp/a.out」
チャー 1 0x00
...
環境 チャー 1 0x00
...
チャー 1 0x00
3. 0x7fffffffe5e0 環境 チャー 1 ..
チャー 1 0x00
...
引数 チャー 1 0x00
...
チャー 1 0x00
4. 0x7fffffffe5be 引数 チャー 1+ 「/tmp/a.out」
5. ランダムな長さの配列
6. auxv のデータ 空所* 48"
AT_NULL Elf64_auxv_t 16 {0,0}
...
補助 Elf64_auxv_t 16
7. 補助 Elf64_auxv_t 16 例: (0x0e,0x3e8)
ヌル 空所* 8 0x00
...
環境 文字* 8
8. 0x7fffffffe308 環境 文字* 8 0x7fffffffe5e0
ヌル 空所* 8 0x00
...
引数 文字* 8
9. 0x7fffffffe2f8 引数 文字* 8 0x7fffffffe5be
10. 0x7fffffffe2f0 argc 長い整数 8" 引数の数 + 1
11. main の前に呼び出される関数のローカル変数と引数
12. ローカル変数メイン
13. 0x7fffffffe1fc argc 整数 4 引数の数 + 1
0x7fffffffe1f0 引数 文字** 8 0x7fffffffe2f8
0x7fffffffe1e8 環境 文字** 8 0x7fffffffe308
14. ローカル関数変数

" - ドキュメント内にフィールドの説明が見つかりませんでしたが、ダンプでははっきりと確認できます。

32 ビットかどうかは確認していませんが、おそらくサイズを 2 で割るだけで十分でしょう。

1. 最上位点より上のアドレスにアクセスすると、セグメンテーション違反が発生します。
2. 実行可能ファイルへのパスを含む文字列。
3. 環境変数を含む文字列の配列
4. コマンドラインパラメータを含む文字列の配列
5. ランダムな長さの配列。 その選択はコマンドで無効にすることができます
sysctl -w kernel.randomize_va_space=0
エコー 0 > /proc/sys/kernel/randomize_va_space
6. 補助ベクトルのデータ (文字列「x86_64」など)
7. 補助ベクトル。 詳細は以下をご覧ください。
8. 環境変数の文字列へのポインタのヌル終端配列
9. コマンドラインパラメータ文字列へのポインタのヌル端子配列
10. コマンドラインパラメータの数を含む機械語 (「主要な」関数の引数の 1 つ、段落 11 を参照)
11. main(_start,__libc_start_main..) の前に呼び出される関数のローカル変数と引数
12. main で宣言された変数
13.main関数の引数
14. ローカル関数の変数と引数。

補助ベクトル
i386 および x86_64 の場合、補助ベクトルの最初の要素のアドレスを取得することはできませんが、このベクトルの内容は他の方法で取得できます。 その 1 つは、環境変数の文字列へのポインターの配列のすぐ後ろにあるメモリ領域にアクセスすることです。
次のようになります。
#含む #含む int main(int argc, char** argv, char** env)( Elf64_auxv_t *auxv; //x86_64 // Elf32_auxv_t *auxv; //i386 while(*env++ != NULL); //先頭を探します補助ベクトル ( auxv = (Elf64_auxv_t *)env; auxv->a_type != AT_NULL; auxv++)( printf("addr: %p type: %lx is: 0x%lx\n", auxv, auxv->a_type, auxv->a_un .a_val); ) printf("\n (void*)(*argv) - (void*)auxv= %p - %p = %ld\n (void*)(argv)-(void* )(&auxv) =%p-%p = %ld\n ", (void*)(*argv), (void*)auxv, (void*)(*argv) - (void*)auxv, (void* )(argv) , (void*)(&auxv), (void*)(argv) - (void*)(&auxv)); printf("\n argc copy: %d\n",*((int *) (argv - 1 ))); 0 を返す; )
Elf(32,64)_auxv_t 構造体は /usr/include/elf.h に記述されています。 linux-kernel/fs/binfmt_elf.c の構造体を埋めるための関数

ベクトルの内容を取得する 2 番目の方法:
hexdump /proc/self/auxv

LD_SHOW_AUXV 環境変数を設定すると、最も読みやすい表現が得られます。

LD_SHOW_AUXV=1 ls
AT_HWCAP: bfebfbff //プロセッサーの機能
AT_PAGESZ: 4096 //メモリページサイズ
AT_CLKTCK: 100 //更新頻度回()
AT_PHDR: 0x400040 //ヘッダー情報
AT_PHENT: 56
AT_PHNUM: 9
AT_BASE: 0x7fd00b5bc000 //インタープリターのアドレス、つまり ld.so
AT_FLAGS: 0x0
AT_ENTRY: 0x402490 //プログラム エントリ ポイント
AT_UID: 1000 //ユーザーおよびグループの識別子
AT_EUID: 1000 //公称および有効
AT_GID: 1000
AT_EGID: 1000
AT_SECURE: 0 //setuid フラグが立てられているかどうか
AT_RANDOM: 0x7fff30bdc809 //ランダムな 16 バイトのアドレス、
起動時に生成される
AT_SYSINFO_EHDR: 0x7fff30bff000 //使用されるページへのポインタ
//システムコール
AT_EXECFN: /bin/ls
AT_PLATFORM: x86_64
左側が変数の名前、右側が値です。 考えられるすべての変数名とその説明は、elf.h ファイルにあります。 (接頭辞 AT_ が付いた定数)

main()からの戻り値
プロセス コンテキストが初期化された後、制御は main() ではなく _start() 関数に移ります。
main() はすでに __libc_start_main から呼び出されています。 この最後の関数には興味深い機能があり、main() の後に実行される関数へのポインタが渡されます。 そして、このポインタはスタックを介して自然に渡されます。
一般に、ファイル glibc-2.11/sysdeps/ia64/elf/start.S によると、__libc_start_main の引数は次のようになります。
/*
* __libc_start_main の引数:
* out0: メイン
* out1: argc
* out2: 引数
* out3: 初期化
* out4: fini //main の後に呼び出される関数
* out5: rtld_fini
* out6: スタックエンド
*/
それらの。 fini ポインタのアドレスを取得するには、最後のローカル変数 main から 2 つのマシンワードをシフトする必要があります。
何が起こったかは次のとおりです (動作可能性はコンパイラのバージョンによって異なります)。
#含む void **ret; void *leave; void foo())( void (*boo)(void); //関数ポインタ printf("Stack rewrite!\n"); boo = (void (*)(void))leave; boo(); // fini () ) int main(int argc, char *argv, char *envp) ( unsigned long int mark = 0xbfbfbfbfbfbfbfbf; //作業対象のマーク ret = (void**)(&mark+2); // address 、完了後に呼び出される関数 (fini) Leave = *ret; // *ret = (void*)foo を覚えておいてください; // Grind return 0; // 関数 foo() を呼び出します )

面白かったら幸いです。
幸運を。

有益なヒントを提供してくれたユーザー Xeor に感謝します。

トピックの続き:
ルーター

Gmail、Mail.ru、Microsoft Outlook、Mozilla Thunderbird などのほとんどの電子メール クライアントでは、複数の受信者を Cc (英語) に入れることができます。