【C++ベースの3】関数における局所変数の戻り
一般的に、関数では局所変数の戻りを行うことができますが、そうでなければすべてグローバル変数を使うのではないでしょうか.グローバル変数を使用している場合は、返す必要がありますか.その関数は存在する意味がありません!しかし、ここでいう局所変数の戻りには内包があり、どのような値が誤りなく返されるのかに注意してください.
実は、一言を守ればいいのです.関数はスタックメモリへのポインタを返すことはできません.
どうして?値コピーが返されるから!
ローカル変数の役割ドメインは関数内部であり,関数が実行されるとスタック上のローカル変数が破棄され,メモリが解放されることを知っている.したがって、このとき関数はローカル変数の値コピーを返します.これは問題ありません.しかし、ローカル変数のアドレスが返されると、返されるのはローカル変数ポインタのコピーだけであり、関数の実行が終了するにつれて、コピーポインタが指すスタックメモリが解放されると、未知の領域を指すと呼び出しのエラーが発生します.
戻ってきたポインタがスタックメモリを指していたらどうなりますか?
このような使用は問題なく,関数内new空間,関数外delete空間である.しかし、これは良いプログラミングスタイルではありません.できるだけ同じ役割ドメイン内でnewとdelete操作を行います.そうしないと、呼び出し者が手動でメモリの解放を行います.このようなインタフェースは腐っているのではないでしょうか.確かにそうする必要があるなら、ポインタを入れましょう.
では、いくつかの典型的な例を見て、局所変数の注意すべき点を返します.
1.正しい.最もnormalの場合.
2.エラー.最もnormalエラー.valueは解放されますが、その値は必ずしもクリアされるとは限らないので、結果も正しいように見えますが、隠れた危険は尽きません.
3.正しい.不思議なことに、「HelloJacky」は文字列定数で、読み取り専用データセグメントに格納されています.returnstrは、読み取り専用データセグメントに存在する文字列の最初のアドレスを返しただけで、関数が終了すると、文字列に存在するメモリは回収されないので、正常です.
4.エラー.この「HelloJacky」はスタック内のローカル変数であり、関数が終了するとメモリが解放されるため、スタック内のローカル変数を返すアドレスが間違っている.
5.正しい.ローカル変数のアドレスを返さなければならない場合はstaticを追加しましょう.
6.エラー、同様に、配列も関数の戻り値として使用できません.配列名は実際にはローカル変数のヘッダアドレスであるためです.
7.正しい.static修飾子を付けましょう.その配列も戻ることができます.
8.正しい.関数内の申請空間は,呼び出し後に空間を解放するが,上述したようにインタフェースが柔軟ではないという欠点がある.
実は、一言を守ればいいのです.関数はスタックメモリへのポインタを返すことはできません.
どうして?値コピーが返されるから!
ローカル変数の役割ドメインは関数内部であり,関数が実行されるとスタック上のローカル変数が破棄され,メモリが解放されることを知っている.したがって、このとき関数はローカル変数の値コピーを返します.これは問題ありません.しかし、ローカル変数のアドレスが返されると、返されるのはローカル変数ポインタのコピーだけであり、関数の実行が終了するにつれて、コピーポインタが指すスタックメモリが解放されると、未知の領域を指すと呼び出しのエラーが発生します.
戻ってきたポインタがスタックメモリを指していたらどうなりますか?
このような使用は問題なく,関数内new空間,関数外delete空間である.しかし、これは良いプログラミングスタイルではありません.できるだけ同じ役割ドメイン内でnewとdelete操作を行います.そうしないと、呼び出し者が手動でメモリの解放を行います.このようなインタフェースは腐っているのではないでしょうか.確かにそうする必要があるなら、ポインタを入れましょう.
では、いくつかの典型的な例を見て、局所変数の注意すべき点を返します.
1.正しい.最もnormalの場合.
int returnValue();
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<returnValue();
return 0;
}
char returnValue()
{
int value=3;
return value;
}
2.エラー.最もnormalエラー.valueは解放されますが、その値は必ずしもクリアされるとは限らないので、結果も正しいように見えますが、隠れた危険は尽きません.
int* returnValue();
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<*(returnValue());
return 0;
}
int* returnValue()
{
int value=3;
return &value;
}
3.正しい.不思議なことに、「HelloJacky」は文字列定数で、読み取り専用データセグメントに格納されています.returnstrは、読み取り専用データセグメントに存在する文字列の最初のアドレスを返しただけで、関数が終了すると、文字列に存在するメモリは回収されないので、正常です.
char* returnValue();
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<returnValue();
return 0;
}
char* returnValue()
{
char* str="HelloJacky";
return str;
}
4.エラー.この「HelloJacky」はスタック内のローカル変数であり、関数が終了するとメモリが解放されるため、スタック内のローカル変数を返すアドレスが間違っている.
char* returnValue();
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<returnValue();
return 0;
}
char* returnValue()
{
char str[]="HelloJacky";
return str;
}
5.正しい.ローカル変数のアドレスを返さなければならない場合はstaticを追加しましょう.
char* returnValue();
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<returnValue();
return 0;
}
char* returnValue()
{
static char str[]="HelloJacky";
return str;
}
6.エラー、同様に、配列も関数の戻り値として使用できません.配列名は実際にはローカル変数のヘッダアドレスであるためです.
int* returnValue();
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<*(returnValue());
return 0;
}
int* returnValue()
{
int value[3]={1,2,3};
return value;
}
7.正しい.static修飾子を付けましょう.その配列も戻ることができます.
int* returnValue();
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<*(returnValue());
return 0;
}
int* returnValue()
{
static int value[3]={1,2,3};
return value;
}
8.正しい.関数内の申請空間は,呼び出し後に空間を解放するが,上述したようにインタフェースが柔軟ではないという欠点がある.
char* newMemory(int size);
int _tmain(int argc, _TCHAR* argv[])
{
char* p=newMemory(2);
if(p!=NULL)
{
*p='a';
}
std::cout<<*p;
delete [] p;
return 0;
}
char* newMemory(int size)
{
char* p=NULL;
p=new char[size];
return p;
}