最初の関数が文字列「Hello、World」を返すのに、2番目の関数は何も返さないのはなぜですか。両方の関数の戻り値は、スコープ外のデータを返すため、未定義になると思いました。
#include <stdio.h>
// This successfully returns "Hello, World"
char* function1()
{
char* string = "Hello, World!";
return string;
}
// This returns nothing
char* function2()
{
char string[] = "Hello, World!";
return string;
}
int main()
{
char* foo1 = function1();
printf("%s\n", foo1); // Prints "Hello, World"
printf("------------\n");
char* foo2 = function2(); // Prints nothing
printf("%s\n", foo2);
return 0;
}
2番目の関数は何も返しません
2番目の関数のstring
配列:
char string[] = "Hello, World!";
自動保存期間を持っています。制御フローが関数から戻った後は存在しません。
一方、最初の関数のstring
:
char* string = "Hello, World!";
静的ストレージ期間のリテラル文字列を指します。これは、関数から戻った後も文字列がまだ存在することを意味します。関数から返すのは、このリテラル文字列へのポインタです。
文字列について最初に学ぶ必要があるのは、文字列リテラルが実際には完全なプログラムの寿命を持つ読み取り専用文字の配列であることです。それは、それらがスコープの外に出ることは決してなく、プログラムの実行中常に存在することを意味します。
最初の関数は何ですか(function1
)は、そのような配列の最初の要素へのポインタを返します。
2番目の関数(function2
)状況は少し異なります。ここで、変数string
は、関数内のローカル変数です。そのため、関数が戻るとスコープから外れて存在しなくなります。この関数を使用すると、その配列の最初の要素へのポインターが返されますが、そのポインターは存在しないものを指すため、すぐに無効になります。それを参照解除すると(printf
に渡すときに発生します)、 undefined behavior になります。
Cまたは他のスタックベースの言語でコーディングする際に覚えておくべき非常に重要なことは、関数が戻ると、関数(およびそのすべてのローカルストレージ)がなくなることです。つまり、メソッドの結果を他の人に見てもらいたい場合は、メソッドが終了した後もまだ存在する場所に配置する必要があります。そのためには、次のことを理解する必要があります。 Cがデータを保存する場所と方法。
おそらくCで配列がどのように動作するかを既に知っているでしょう。それはオブジェクトのサイズだけ増加するメモリアドレスであり、Cが境界チェックを行わないことも知っているので、10の11番目の要素にアクセスしたい場合要素配列、誰もあなたを止めるつもりはありません、そしてあなたが何かを書こうとしない限り、害はありません。知らないかもしれないのは、Cがこの考えを関数と変数の使用方法にまで拡張していることです。関数は、オンデマンドでロードされるスタック上のメモリ領域にすぎず、変数のストレージはその場所からのオフセットにすぎません。関数がローカル変数へのポインター、具体的には、「Hello World\n\0」の「H」を保持するスタック上の場所のアドレスを返しましたが、そのときに別の関数(printメソッド)を呼び出したとき、そのメモリは必要なことを行うためにprintメソッドによって再利用されます。あなたはこれを十分に簡単に見ることができます(生産コードでこれをしないでください!!!)
char* foo2 = function2(); // Prints nothing
ch = foo2[0]; // Do not do this in live code!
printf("%s\n", foo2); // stack used by foo2 now used by print()
printf("ch is %c\n", ch); // will have the value 'H'!
両方の関数の戻り値は、スコープ外のデータを返すため、未定義になると思いました。
いいえ。そうではありません。
関数内function1
あなたは文字列リテラルへのポインタを返しています。文字列リテラルにはstatic storage durationがあるため、文字列リテラルへのポインターを返すのは問題ありません。しかし、それは自動ローカル変数には当てはまりません。
関数内function2
配列string
は自動ローカル変数であり、ステートメント
return string;
自動ローカル変数へのポインタを返します。関数が戻ると、変数string
は存在しなくなります。返されたポインターを逆参照すると、未定義の動作が発生します。
"Hello, World!"
は文字列リテラルであり、静的な保存期間があるため、問題は別の場所にあります。最初の関数は、string
の-valueを返しますが、これは問題ありません。ただし、2番目の関数は、ローカル変数のaddressを返します(string
は&string[0]
と同じです)、未定義の動作になります。 2番目のprintf
ステートメントは、何も印刷しないか、「Hello、World!」またはまったく別のものを印刷できます。私のマシンでは、プログラムにセグメンテーションエラーが発生します。
コンパイラが出力するメッセージを常に確認してください。たとえば、gcc
は次のようになります。
file.c:12:12: warning: function returns address of local variable [-Wreturn-local-addr]
return string;
^
それはほとんど自明です。
両方の関数の戻り値は、スコープ外のデータを返すため、未定義になると思いました。
両方の関数はポインターを返します。重要なのは、referentのスコープです。
function1
、指示対象は文字列リテラル"Hello, World!"
、これは静的な保存期間を持ちます。 string
はその文字列を指すローカル変数であり、概念的には、そのポインターのコピーが返されます(実際には、コンパイラーは不必要に値をコピーすることを避けます)。
function2
、概念的には、参照先はローカル配列string
であり、文字列リテラル(もちろんnullターミネーターを含む)を保持するのに十分な大きさになるように(コンパイル時に)自動的にサイズ設定され、文字列のコピー。関数wouldは、その配列へのポインタを返します。ただし、配列には自動保存期間があり、関数の終了後は存在しません(実際には「より一般的な用語で)これは未定義の動作であるため、コンパイラ 実際にはあらゆる種類のことを行う可能性があります 。
つまり、すべての
char*
は静的ですか?
繰り返しますが、ポインタと指示対象を区別する必要があります。ポインターはデータを指します。彼らはそれ自体がデータを「含んでいない」。
配列とポインターが実際にCにあるものを適切に研究する必要があります-残念ながら、それは少し混乱しています。私がオフハンドで提供できる最良のリファレンスは、Q&A形式の this です。