52

静的ローカル変数へのポインターを返すというイディオムを広く使用しているコードを使用しています。例えば:

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}

私はこれが安全だと思っているのは正しいですか?

PS、私はこれが同じことをするためのより良い方法であることを知っています:

char* const GetString()
{
  return "Test";
}

編集: お詫びします。関数の署名はもちろん次のようにする必要があります。

const char* GetString();
4

7 に答える 7

41

最初の例: やや安全

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}

お勧めしませんが、これは安全です。静的変数のスコープは、関数のスコープが終了しても有効なままです。この関数はまったくスレッドセーフではありません。より良い関数では、関数が満たす ために achar* bufferと aを渡す必要があります。maxsizeGetString()

特に、この関数は再入可能関数とは見なされません。これは、再入可能関数は、とりわけ静的 (グローバル) 非定数データへのアドレスを返してはならないためです。再入可能関数を参照してください。

2 番目の例: 完全に危険

char* const GetString()
{
  return "Test";
}

を実行した場合、これは安全ですconst char *。あなたが与えたものは安全ではありません。その理由は、文字列リテラルが読み取り専用メモリ セグメントに格納される可能性があり、それらを変更できるようにすると、未定義の結果が生じるためです。

char* const(const pointer) は、ポインターが指しているアドレスを変更できないことを意味します。 const char *(const へのポインター) は、このポインターが指している要素を変更できないことを意味します。

結論:

次のいずれかを検討する必要があります。

1) コードにアクセスできる場合は、 を変更して、入力GetStringするパラメータとchar* buffer使用するパラメータを取得しmaxsizeます。

2) コードにアクセスできないが、それを呼び出す必要がある場合は、このメソッドをミューテックスによって保護されている別の関数でラップします。新しい方法は 1 で説明したとおりです。

于 2009-01-17T18:10:41.250 に答える
10

static変数 (関数内) はスコープ付きグローバル変数のようなものです。一般に、それらは避けるべきです (グローバル変数と同様に、再入の問題を引き起こします) が、時には有用です (一部の標準ライブラリ関数がそれらを使用します)。グローバル変数へのポインターを返すことができるので、変数へのポインターも返すことができますstatic

于 2009-01-17T18:04:47.330 に答える
10

安全とは何を意味するかによります。すぐにわかる問題がいくつかあります。

  1. を返しましたchar * const。これにより、発信者はこの場所で文字列を変更できます。潜在的なバッファ オーバーラン。それとも、ということconst char *ですか?
  2. 再入可能性または並行性に問題がある可能性があります。

2番目を説明するには、次のことを考慮してください。

const char * const format_error_message(int err)
{
    static char error_message[MAXLEN_ERROR_MESSAGE];
    sprintf(error_message, "Error %#x occurred", err);
    return error_message;
}

次のように呼び出す場合:

int a = do_something();
int b = do_something_else();

if (a != 0 && b != 0)
{
    fprintf(stderr,
        "do_something failed (%s) AND do_something_else failed (%s)\n",
        format_error_message(a), format_error_message(b));
} 

...何が印刷されますか?

スレッド化についても同じです。

于 2009-01-17T18:12:48.283 に答える
8

基本的に、はい、静的であるため、値が無期限に続くという意味で安全です。

定数データへの変数ポインターではなく、変数データへの定数ポインターを返したという意味では安全ではありません。呼び出し元の関数がデータを変更することを許可しない方がよいでしょう:

const char *GetString(void)
{
    static char sTest[5];
    strncpy(sTest, "Test", sizeof(sTest)-1);
    sTest[sizeof(sTest)-1] = '\0';
    return sTest;
}

示されている単純なケースでは、バッファ オーバーフローを心配する必要はほとんどありませんが、コードの私のバージョンは心配し、null 終了を保証します。代わりに、 TR24731関数を使用することもできます。strcpy_s

const char *GetString(void)
{
    static char sTest[5];
    strcpy_s(sTest, sizeof(sTest), "Test");
    return sTest;
}

さらに重要なことは、両方のバリアントが定数データへの (変数) ポインターを返すため、ユーザーは文字列を変更したり、(おそらく) 配列の範囲外を踏みつぶしたりしないでください。(@strager がコメントで指摘しているように、a を返すことconst char *は、ユーザーが返されたデータを変更しようとしないことを保証するものではありません。ただし、返されたポインターをキャストして非定数にしてからデータを変更する必要があります。これは未定義の動作を引き起こし、その時点で何でも可能です。)

リテラル戻り値の利点の 1 つは、通常、コンパイラとオペレーティング システムによって書き込み禁止の約束を強制できることです。文字列はプログラムのテキスト (コード) セグメントに配置され、戻り値が指すデータをユーザーが変更しようとすると、オペレーティング システムはエラー (Unix ではセグメンテーション違反) を生成します。

[他の回答の少なくとも 1 つは、コードが再入可能ではないことを示しています。それは正しいです。リテラルを返すバージョンは再入可能です。再入可能性が重要な場合は、呼び出し元がデータを格納するスペースを提供できるようにインターフェイスを修正する必要があります。]

于 2009-01-17T18:07:38.200 に答える
3

はい、完全に安全です。ローカル static の有効期間は、C でのプログラム実行全体の有効期間です。そのため、関数が戻った後でも配列が生きているため、ポインターを返すことができ、返されたポインターは有効に逆参照できます。

于 2009-01-17T18:06:03.897 に答える
1

関数をprintfパラメータとして直接使用できるため、非常に便利です。ただし、前述のとおり、1 回の呼び出しで関数を複数回呼び出すと問題が発生します。これは、関数が同じストレージを使用し、2 回呼び出すと返された文字列が上書きされるためです。しかし、私はこのコードをテストしましたが、うまくいくようです - 関数を安全に呼び出すことができます.

#define MAX_CALLS 3
#define MAX_LEN 30

char *givemestring(int num)
{
        static char buf[MAX_CALLS][MAX_LEN];
        static int rotate=0;

        rotate++;
        rotate%=sizeof(buf)/sizeof(buf[0]);

        sprintf(buf[rotate],"%d",num);
        return buf[rotate];

}

唯一の問題はスレッド セーフですが、これはスレッド ローカル変数 (gcc の __thread キーワード) で解決できます。

于 2013-03-15T09:47:28.683 に答える
0

はい、これはルックアップのテキスト部分を返すために頻繁に使用されます。つまり、エラー番号をわかりやすい文字列に変換するために使用されます。

次のような場合にこれを行うのが賢明です。

fprintf(stderr, "Error was %s\n", my_string_to_error(error_code));

割り当てられた文字列が返された場合my_string_to_error()、そのような関数の上記の(非常に)一般的な使用法を考えると、プログラムはリークします。

char const *foo_error(...)
{
    return "Mary Poppins";
}

... も問題ありませんが、一部の脳死コンパイラでは、キャストする必要がある場合があります。

この方法で文字列を見てください。本を返さないでください:)

于 2009-01-17T18:05:38.717 に答える