C/C++内部静的メンバーを返すトラップ
3757 ワード
原文住所:http://blog.csdn.net/xluren/article/details/8170273
私たちがC/C++で開発する過程で、いつも一つの問題が私たちに悩みをもたらします.この問題は、関数内と関数外のコードがメモリを介してインタラクティブになる必要があることです(たとえば、関数が文字列を返すなど)、この問題は多くの開発者に迷惑をかけています.メモリが関数内スタックに割り当てられている場合、このメモリは関数の戻りに伴ってポップスタックによって解放されます.そのため、関数の外部に有効なメモリを返さなければなりません.
これは無数の人を困らせる問題だ.うっかりすると、この上で間違いを犯す可能性が高い.もちろん、現在は多くの解決策がありますが、標準ライブラリを熟知している場合は、さまざまな解決策が見られます.大体次のようなものがあります.
1)関数内部でmallocまたはnewを介してスタックにメモリを割り当て、そのメモリを返します(スタックに割り当てられたメモリはグローバルに表示されるため).これにより、潜在的なメモリの問題が発生します.
なぜなら、戻ってきたメモリが解放されないと、memory Leakになるからです.あるいは何度も解放され、プログラムのcrashをもたらします.この2つの問題はかなり深刻なので、この設計方法はお勧めしません.(一部のWindows APIでは、いくつかのAPIを呼び出すと、このメモリを解放するためにいくつかのAPIを呼び出す必要があります)
2)ユーザに自分のメモリアドレスを入力させ,関数で返すメモリをこのメモリに格納する.これは現在一般的に使われている方法です.多くのWindows API関数や標準C関数では、bufferとこのbufferの長さを入力する必要があります.この方法は私たちにとって珍しくないはずだ.この方法の利点は、関数の外部のプログラムによってこのメモリを維持し、比較的直感的であることです.しかし、問題は使用上少し面倒だということです.しかし、この方式は過ちを犯す確率を最小限に抑えた.
3)3つ目の方法は、staticの特性を利用しています.staticのスタックメモリが割り当てられると、このメモリは関数の戻りに伴って解放されません.また、このメモリのアドレスがある限り、グローバルに表示されます.したがって、スタック上のメモリを使用する必要がなく、bufferとその長さをユーザーが入力する必要もないstaticのこの特性を使用する関数もあります.したがって,自分の関数を使ってきれいに育ち,使いやすい.
ここでは、3つ目の方法についていくつか議論したいと思います.staticメモリを使うという方法はよさそうですが、想像できない落とし穴があります.実際に起こった例を挙げてみましょう.
例
ソケットプログラミングの経験がある人は、inetという関数を知っているに違いありません.ntoa、この関数の主な機能はデジタル型のIPアドレスを文字列に変換することであり、この関数の定義はこのようなものである(その戻り値に注意):
明らかに、この関数はスタック上のメモリを割り当てませんが、文字列のbufferを転送させていません.彼は必ず「static char[]に戻る」という方法を使用します.議論を続ける前に、IPアドレスに関する知識を理解しておきましょう.次はinet_です.ntoaという関数には伝達されるパラメータが必要です:(不思議かもしれませんが、1つのメンバーのstructだけがstructの中に置いて何をしますか?これはプログラムの将来の拡張性のための考慮であるはずです)
IPV 4の場合、1つのIPアドレスは、s_に配置された4つの8ビットのbitからなるaddrでは,ネットワーク伝送を容易にするために上位に位置する.もしあなたがsを手に入れたらaddrの整数値は3776385196です.では、Windows計算機を開けて、バイナリが何なのか見てみましょう.右から左へ、8桁を1組(以下に示す)にしましょう.
11100001 00010111 00010000 10101100
各グループを10進数に変換すると、225,236,172が得られ、IPアドレスは172.16.3.225になります.
さあ、本題に戻ります.ネットワークパケットのソースアドレスと宛先アドレスを記録したいプログラムがあります.そこで、次のコードがあります.
どのような結果になるのでしょうか.ファイルに記録されているソースIPアドレスと宛先IPアドレスはまったく同じであることがわかります.これは何の問題ですか.プログラムのデバッグを始めたらsrc.s_addrとdes.s_addrはまったく違います(以下に示します).しかし、なぜファイルに出力されるソースと目的は同じですか?もしかしてinet_ntoaのバグ?
理由はinet_ntoa()は内部のstatic char[]を「自業自得」に返したが,我々のプログラムはまさにこの罠を踏んだ.fprintfコードを分析してみましょう.fprintfでは、コンパイラがinet_を計算します.ntoa(des)は、文字列のアドレスを返し、inet_を求めます.ntoa(src)式は、文字列のアドレスを返します.
この2つの文字列のアドレスはinet_です.ntoa()のstatic char[]は、明らかに同じアドレスであり、2回目にsrcのIPを求めると、この値のdesのIPアドレスの内容は必ずsrcのIPで上書きされます.したがって、この2つの式の文字列メモリは同じであり、プログラムはfprintfを呼び出してこの2つの文字列(実は1つ)をファイルに出力します.だから、同じ結果を得るのもおかしくない.
よく見てごらんntoaのman、私たちはこの言葉を見ることができます:The string is returned in a statically allocated buffer、which subsequent calls will overwrite.は私たちの分析を確認しました.
小結
プログラムを書く過程でこの方法を使ったかどうか、皆さんに自問してみましょう.これは比較的危険で、間違いやすい方法です.この罠は防ぎようがない.このようなプログラムがあれば
2つのIPアドレスが同じかどうかを判断しようとしたが、その罠に落ちた--この条件式を永遠に真にする.このことは私たちに以下のいくつかの道理を教えてくれた:1)このような方法の設計を慎む.戻り関数内部のstaticメモリには大きなトラップがあります.2)どうしてもこの方式を使うなら.この関数を使用しているすべての人に、1つの式でこの関数を何度も使用しないでください.また、copy関数で返されるメモリの内容ではなく、返されるメモリアドレスや参照を保存するだけでは役に立たないことを伝えます.そうでなければ、結果は一切責任を負いません.3)C/C++は危険な世界です.もしあなたが彼を知らないなら.やはり火星に帰りましょう.
私たちがC/C++で開発する過程で、いつも一つの問題が私たちに悩みをもたらします.この問題は、関数内と関数外のコードがメモリを介してインタラクティブになる必要があることです(たとえば、関数が文字列を返すなど)、この問題は多くの開発者に迷惑をかけています.メモリが関数内スタックに割り当てられている場合、このメモリは関数の戻りに伴ってポップスタックによって解放されます.そのため、関数の外部に有効なメモリを返さなければなりません.
これは無数の人を困らせる問題だ.うっかりすると、この上で間違いを犯す可能性が高い.もちろん、現在は多くの解決策がありますが、標準ライブラリを熟知している場合は、さまざまな解決策が見られます.大体次のようなものがあります.
1)関数内部でmallocまたはnewを介してスタックにメモリを割り当て、そのメモリを返します(スタックに割り当てられたメモリはグローバルに表示されるため).これにより、潜在的なメモリの問題が発生します.
なぜなら、戻ってきたメモリが解放されないと、memory Leakになるからです.あるいは何度も解放され、プログラムのcrashをもたらします.この2つの問題はかなり深刻なので、この設計方法はお勧めしません.(一部のWindows APIでは、いくつかのAPIを呼び出すと、このメモリを解放するためにいくつかのAPIを呼び出す必要があります)
2)ユーザに自分のメモリアドレスを入力させ,関数で返すメモリをこのメモリに格納する.これは現在一般的に使われている方法です.多くのWindows API関数や標準C関数では、bufferとこのbufferの長さを入力する必要があります.この方法は私たちにとって珍しくないはずだ.この方法の利点は、関数の外部のプログラムによってこのメモリを維持し、比較的直感的であることです.しかし、問題は使用上少し面倒だということです.しかし、この方式は過ちを犯す確率を最小限に抑えた.
3)3つ目の方法は、staticの特性を利用しています.staticのスタックメモリが割り当てられると、このメモリは関数の戻りに伴って解放されません.また、このメモリのアドレスがある限り、グローバルに表示されます.したがって、スタック上のメモリを使用する必要がなく、bufferとその長さをユーザーが入力する必要もないstaticのこの特性を使用する関数もあります.したがって,自分の関数を使ってきれいに育ち,使いやすい.
ここでは、3つ目の方法についていくつか議論したいと思います.staticメモリを使うという方法はよさそうですが、想像できない落とし穴があります.実際に起こった例を挙げてみましょう.
例
ソケットプログラミングの経験がある人は、inetという関数を知っているに違いありません.ntoa、この関数の主な機能はデジタル型のIPアドレスを文字列に変換することであり、この関数の定義はこのようなものである(その戻り値に注意):
char *inet_ntoa(struct in_addr in);
明らかに、この関数はスタック上のメモリを割り当てませんが、文字列のbufferを転送させていません.彼は必ず「static char[]に戻る」という方法を使用します.議論を続ける前に、IPアドレスに関する知識を理解しておきましょう.次はinet_です.ntoaという関数には伝達されるパラメータが必要です:(不思議かもしれませんが、1つのメンバーのstructだけがstructの中に置いて何をしますか?これはプログラムの将来の拡張性のための考慮であるはずです)
struct in_addr
{
unsigned long int s_addr;
}
IPV 4の場合、1つのIPアドレスは、s_に配置された4つの8ビットのbitからなるaddrでは,ネットワーク伝送を容易にするために上位に位置する.もしあなたがsを手に入れたらaddrの整数値は3776385196です.では、Windows計算機を開けて、バイナリが何なのか見てみましょう.右から左へ、8桁を1組(以下に示す)にしましょう.
11100001 00010111 00010000 10101100
各グループを10進数に変換すると、225,236,172が得られ、IPアドレスは172.16.3.225になります.
さあ、本題に戻ります.ネットワークパケットのソースアドレスと宛先アドレスを記録したいプログラムがあります.そこで、次のコードがあります.
struct in_addr src, des;
........
........
fprintf(fp, " IP <%s>\t IP <%s>
", inet_ntoa(src), inet_ntoa(des));
どのような結果になるのでしょうか.ファイルに記録されているソースIPアドレスと宛先IPアドレスはまったく同じであることがわかります.これは何の問題ですか.プログラムのデバッグを始めたらsrc.s_addrとdes.s_addrはまったく違います(以下に示します).しかし、なぜファイルに出力されるソースと目的は同じですか?もしかしてinet_ntoaのバグ?
src.s_addr = 3776385196; // 172.16.23.225
des.s_addr = 1678184620; // 172.16.7.100
理由はinet_ntoa()は内部のstatic char[]を「自業自得」に返したが,我々のプログラムはまさにこの罠を踏んだ.fprintfコードを分析してみましょう.fprintfでは、コンパイラがinet_を計算します.ntoa(des)は、文字列のアドレスを返し、inet_を求めます.ntoa(src)式は、文字列のアドレスを返します.
この2つの文字列のアドレスはinet_です.ntoa()のstatic char[]は、明らかに同じアドレスであり、2回目にsrcのIPを求めると、この値のdesのIPアドレスの内容は必ずsrcのIPで上書きされます.したがって、この2つの式の文字列メモリは同じであり、プログラムはfprintfを呼び出してこの2つの文字列(実は1つ)をファイルに出力します.だから、同じ結果を得るのもおかしくない.
よく見てごらんntoaのman、私たちはこの言葉を見ることができます:The string is returned in a statically allocated buffer、which subsequent calls will overwrite.は私たちの分析を確認しました.
小結
プログラムを書く過程でこの方法を使ったかどうか、皆さんに自問してみましょう.これは比較的危険で、間違いやすい方法です.この罠は防ぎようがない.このようなプログラムがあれば
if ( strcmp( inet_ntoa(ip1), inet_ntoa(ip2) )==0 )
{
.... ....
}
2つのIPアドレスが同じかどうかを判断しようとしたが、その罠に落ちた--この条件式を永遠に真にする.このことは私たちに以下のいくつかの道理を教えてくれた:1)このような方法の設計を慎む.戻り関数内部のstaticメモリには大きなトラップがあります.2)どうしてもこの方式を使うなら.この関数を使用しているすべての人に、1つの式でこの関数を何度も使用しないでください.また、copy関数で返されるメモリの内容ではなく、返されるメモリアドレスや参照を保存するだけでは役に立たないことを伝えます.そうでなければ、結果は一切責任を負いません.3)C/C++は危険な世界です.もしあなたが彼を知らないなら.やはり火星に帰りましょう.