0

C の配列を関数に渡すことに関して質問があります。

このプログラムを実行すると、セグメンテーション違反が発生します

int main()
{

  char **ptr= NULL;
  test(ptr);
  printf("%s", ptr[0]);
  return 0;
}

void test(char **ptr)
{
  ptr = (char **) malloc(sizeof(char *));
  ptr[0] = (char *) malloc(sizeof(char)*5);
  strcpy(ptr[0], "abc");
  return;

}

しかし、これはうまくいきました

int main()
{

  char **ptr= NULL;
  test(&ptr);
  printf("%s", ptr[0]);
  return 0;
}

void test(char ***ptr)
{
  *ptr = (char **) malloc(sizeof(char *));
  *ptr[0] = (char *) malloc(sizeof(char)*5);
  strcpy(*ptr[0], "abc");
  return;

}

誰かが理由を説明できますか?

4

5 に答える 5

2

C では、配列を関数に渡すと、配列はポインターに "減衰" します。この関数は、配列の最初の要素へのポインターを受け取ります。配列自体は受け取りません。呼び出された関数でポインターの割り当てに加えられた変更は、呼び出し元の関数には反映されません。

最初の例でtest()は、文字列の配列を受け取り、関数内でそのポインターが指すものを変更します。したがって、 のローカル コピーはptr、 とNULL同様に、関数内で割り当てられたメモリを取得しptr[0]ます。ただし、これらの変更は に対してローカルtest()です。呼び出し関数には反映されません。test()実行が終了して戻るときptr、呼び出し関数の の値はまだNULLです。で割り当てられたメモリにアクセスする方法がないため、メモリ リークが発生しますtest()

呼び出し元の関数に変更が反映されるようにするには、文字列の配列へのポインターを渡す必要があります。したがって、&ptr呼び出しの と の定義の 3 レベル ポインターですtest()。別のより簡単なアプローチは次のとおりです。

int main()
{

    char **ptr= NULL;
    ptr = test();
    printf("%s", ptr[0]);
    return 0;
}

char** test(void)
{
      char **ptr = (char **) malloc(sizeof(char *));
      ptr[0] = (char *) malloc(sizeof(char) * 5);
      strcpy(ptr[0], "abc");
      return ptr;

}

1 つの明確化: 「呼び出された関数のポインター割り当てに加えられた変更は、呼び出し元の関数には反映されません」と述べました。これは、「配列要素への変更は、呼び出し元の関数に反映されない」ということと同じではありません。このことを考慮:

int main (void) {

    int array [] = { 0, 1, 2, 3, 4 };

    test1 (array);
    printf ("%d\n", *array);

    test2 (array);
    printf ("%d\n", *array);

    return 0;
}

void test1 (int* array) {

    int new_array[] = { 3, 4, 5, 6, 7 };
    array = new_array;
    return;
}

void test2 (int* array) {

    array [0] = 5;    // or *array = 5
    array [1] = 6;
    array [2] = 7;
    array [3] = 8;
    array [4] = 9;

    return;
}

出力:

0
5

ここでtest1()の変更は、配列ポインター自体が指している場所であり、呼び出し元には反映されません。そのため、呼び出し元の関数で*testは 0 のままです。test2()では、変更は配列要素に対して行われ、呼び出し元に反映されます。したがって、output: の違いは*test5 になりました。これはすべて、動的メモリ割り当てではなく静的メモリ割り当てを使用しますが、原則は同じです。

于 2013-08-22T19:44:07.240 に答える
1

これは、関数が値を返すのではなく、別のスコープで既に作成した変数を変更することを期待しているためです。

ptr関数から、次の構造が渡されて終了することが期待されます。

ptr -> b -> []

b配列へのポインタ[]です。

最初の例では、関数内の外側の ptr を変更しているのではなく、そのコピーを変更しています。その場合、目的の構造を取得するには、割り当てられたポインターを返すことができるように、関数が char** を返す必要があります。また、元の ptr を渡す必要はありません。

一方、2 番目の例では、ポインターを外側の ptr に渡しているため、関数は外側の ptr が使用するのと同じメモリ空間を変更します。

これが少し役立つことを願っています。

于 2013-08-22T18:22:47.707 に答える
0

@rcrmn からの回答を拡張するには。

呼び出し構造の「上」にデータが渡されるようにすることは、優れたプログラム設計です。この場合main()、テストから受け取ったストレージを解放する必要があります。

このコードは、元の質問のすべての二重ポインターを回避し、要件を満たしていることに注意してください。これが優れたプログラミングの本質であり、仕事を成し遂げる明確でシンプルなコードを書きます。

#include <stdio.h>
char *test();
int main()
{
  char *ptr = test();
  printf("%s", ptr);
  free(ptr);
  return 0;
}

char *test()
{
  char *ptr = (char *) malloc(sizeof(char) * 5);
  strncpy(ptr, "abc", 3);
  return ptr;
}
于 2013-08-22T20:51:31.680 に答える