[c] 스택 배열의 ​​포인터에 액세스 할 수없는 이유는 무엇입니까?

다음 코드를 살펴보십시오. 배열을 char**함수 로 전달하려고 합니다.

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("Test: %c\n", (*x)[0]);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    test[0] = 'B';
    test2[0] = 'A';

    printchar(&test2);            // works
    printchar((char **) &test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

이미 명시 적으로 캐스팅 &test2하여 컴파일 할 수 있다는 사실은 char**이 코드가 잘못되었음을 암시합니다.

아직도, 나는 그것에 대해 정확히 무엇이 잘못되었는지 궁금합니다. 동적으로 할당 된 배열에 대한 포인터에 대한 포인터를 전달할 수 있지만 스택의 배열에 대한 포인터에 대한 포인터는 전달할 수 없습니다. 물론 배열을 임시 변수에 먼저 할당하여 문제를 쉽게 해결할 수 있습니다.

char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);

그래도 누군가 char[256]char**직접 전송 하지 않는 이유를 설명해 줄 수 있습니까?



답변

때문에이 test포인터가 아닙니다.

&test배열이 포인터가 아니기 때문에 char (*)[256]호환되지 않는 유형의 배열에 대한 포인터를 가져 char**옵니다. 결과적으로 정의되지 않은 동작이 발생합니다.


답변

test포인터가 아닌 배열이며 배열에 대한 포인터 &test입니다. 포인터를 가리키는 포인터가 아닙니다.

배열이 포인터라는 말을 들었을 수도 있지만 이는 잘못된 것입니다. 배열의 이름은 전체 객체, 모든 요소의 이름입니다. 첫 번째 요소에 대한 포인터가 아닙니다. 대부분의 표현식에서 배열은 첫 번째 요소에 대한 포인터로 자동 변환됩니다. 그것은 종종 유용한 편의입니다. 그러나이 규칙에는 세 가지 예외가 있습니다.

  • 배열은의 피연산자입니다 sizeof.
  • 배열은의 피연산자입니다 &.
  • 배열은 배열을 초기화하는 데 사용되는 문자열 리터럴입니다.

에서는 &test, 배열의 피연산자는 &자동 전환이 발생하지 않도록. 의 결과 는 형식 이 아닌 형식 &test이 256 char인 배열에 대한 포인터 입니다.char (*)[256]char **

포인터에 대한 포인터를 얻으려면 char에서 test먼저 포인터를 만들 필요가있다 char. 예를 들면 다음과 같습니다.

char *p = test; // Automatic conversion of test to &test[0] occurs.
printchar(&p);  // Passes a pointer to a pointer to char.

이것에 대해 생각하는 또 다른 방법 test은 전체 객체의 이름, 즉 전체 배열의 이름을 256으로 인식하는 것 char입니다. 포인터의 이름을 지정하지 않으므로에 &test주소를 가져올 수있는 포인터가 없으므로을 (를) 생성 할 수 없습니다 char **. 를 만들려면 char **먼저가 있어야합니다 char *.


답변

의 유형은 test2입니다 char *. 따라서 유형&test2 할 예정입니다 char **매개 변수의 유형과 호환 xprintchar().
의 유형은 test입니다 char [256]. 그래서, 유형 &test것입니다 수 char (*)[256]있습니다 되지 매개 변수의 유형과 호환 xprintchar().

test및 주소의 차이점을 보여 드리겠습니다 test2.

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("x = %p\n", (void*)x);
    printf("*x  = %p\n", (void*)(*x));
    printf("Test: %c\n", (*x)[0]);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    test[0] = 'B';
    test2[0] = 'A';

    printf ("test2 : %p\n", (void*)test2);
    printf ("&test2 : %p\n", (void*)&test2);
    printf ("&test2[0] : %p\n", (void*)&test2[0]);
    printchar(&test2);            // works

    printf ("\n");
    printf ("test : %p\n", (void*)test);
    printf ("&test : %p\n", (void*)&test);
    printf ("&test[0] : %p\n", (void*)&test[0]);

    // Commenting below statement
    //printchar((char **) &test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

산출:

$ ./a.out
test2 : 0x7fe974c02970
&test2 : 0x7ffee82eb9e8
&test2[0] : 0x7fe974c02970
x = 0x7ffee82eb9e8
*x  = 0x7fe974c02970
Test: A

test : 0x7ffee82eba00
&test : 0x7ffee82eba00
&test[0] : 0x7ffee82eba00

여기를 참고하십시오 :

출력 (메모리 어드레스) test2&test2[0]수치 같은 그들의 타입 인도 동일하다 char *.
그러나 test2&test2 주소가 다르고 유형도 다릅니다.
의 유형은 test2입니다 char *.
의 유형은 &test2입니다 char **.

x = &test2
*x = test2
(*x)[0] = test2[0] 

출력 (메모리 어드레스) test, &test&test[0]수치 동일 하지만, 그들의 형태는 다르다 .
의 유형은 test입니다 char [256].
의 유형은 &test입니다 char (*) [256].
의 유형은 &test[0]입니다 char *.

출력 도시 된 바와 같이 &test동일하다 &test[0].

x = &test[0]
*x = test[0]       //first element of test array which is 'B'
(*x)[0] = ('B')[0]   // Not a valid statement

따라서 세그먼테이션 오류가 발생합니다.


답변

포인터가 아니기 때문에 포인터에 대한 포인터에 액세스 할 수 없습니다 &test. 그것은 배열입니다.

배열의 주소를 가져 와서 배열과 배열의 주소를로 캐스팅 (void *)하고 비교하면 가능한 포인터 포인터를 제외하고 동일합니다.

실제로하고있는 것은 이것과 비슷합니다 (다시 말해서 엄격한 앨리어싱 제외).

putchar(**(char **)test);

이것은 분명히 잘못된 것입니다.


답변

귀하의 코드는 인수 가 포함 된 메모리를 가리키는 인수 x를 기대합니다 printchar.(char *) .

첫 번째 호출에서 이는 사용 된 스토리지 test2를 가리 키므로 실제로 (char *)는를 가리키는 값 이며, 후자는 할당 된 메모리를 가리 킵니다.

그러나 두 번째 호출에서는 이러한 (char *)값이 저장 될 수있는 위치가 없으므로 해당 메모리를 가리킬 수 없습니다. (char **)추가 한 캐스트에서 컴파일 오류 (로 변환 (char *)하는 방법 에 대한 오류)를 제거 (char **)했지만 저장 공간이 얇은 공기에서(char *) 시험의 첫 번째 문자를 가리 키도록 초기화. C에서 포인터 캐스팅은 포인터의 실제 값을 변경하지 않습니다.

원하는 것을 얻으려면 명시 적으로해야합니다.

char *tempptr = &temp;
printchar(&tempptr);

나는 당신의 예제가 훨씬 더 큰 코드의 증류라고 가정합니다. 예를 들어, 다음 호출에서 다음 문자가 인쇄되도록 전달 된 값이 가리키는 값 printchar을 증가 시키려고 할 수 있습니다 . 그렇지 않은 경우 인쇄 할 문자를 가리 키거나 문자 자체를 전달 하지 않는 이유는 무엇입니까?(char *)x(char *)


답변

주소를받는 것은 주소를 test받는 것과 test[0]같습니다.

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("[printchar] Address of pointer to pointer: %p\n", (void *)x);
    printf("[printchar] Address of pointer: %p\n", (void *)*x);
    printf("Test: %c\n", **x);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    printf("[main] Address of test: %p\n", (void *)test);
    printf("[main] Address of the address of test: %p\n", (void *)&test);
    printf("[main] Address of test2: %p\n", (void *)test2);
    printf("[main] Address of the address of test2: %p\n", (void *)&test2);

    test[0] = 'B';
    test2[0] = 'A';

    printchar(&test2);            // works
    printchar(&test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

그것을 컴파일하고 실행하십시오 :

forcebru$ clang test.c -Wall && ./a.out
test.c:25:15: warning: incompatible pointer types passing 'char (*)[256]' to
      parameter of type 'char **' [-Wincompatible-pointer-types]
    printchar(&test);   // crashes because *x in printchar() has an inva...
              ^~~~~
test.c:4:30: note: passing argument to parameter 'x' here
static void printchar(char **x)
                             ^
1 warning generated.
[main] Address of test: 0x7ffeeed039c0
[main] Address of the address of test: 0x7ffeeed039c0 [THIS IS A PROBLEM]
[main] Address of test2: 0x7fbe20c02aa0
[main] Address of the address of test2: 0x7ffeeed039a8
[printchar] Address of pointer to pointer: 0x7ffeeed039a8
[printchar] Address of pointer: 0x7fbe20c02aa0
Test: A
[printchar] Address of pointer to pointer: 0x7ffeeed039c0
[printchar] Address of pointer: 0x42 [THIS IS THE ASCII CODE OF 'B' in test[0] = 'B';]
Segmentation fault: 11

따라서 세그먼테이션 오류의 궁극적 인 원인은이 프로그램이 절대 주소 0x42(또는'B' 읽을 권한이없는 ) 입니다.

다른 컴파일러 / 머신을 사용하더라도 주소는 다를 수 있습니다. 온라인으로 시도하십시오! 하지만 어떤 이유로 든 여전히 얻을 수 있습니다.

[main] Address of test: 0x7ffd4891b080
[main] Address of the address of test: 0x7ffd4891b080  [SAME ADDRESS!]

그러나 분할 오류를 일으키는 주소는 매우 다를 수 있습니다.

[printchar] Address of pointer to pointer: 0x7ffd4891b080
[printchar] Address of pointer: 0x9c000000942  [WAS 0x42 IN MY CASE]


답변

표현은 char [256]구현 의존적이다. 같지 않아야합니다.char * .

주조 &test유형 char (*)[256]char ** 하면 정의되지 않은 동작이 발생합니다.

일부 컴파일러를 사용하면 예상대로 수행 할 수 있지만 다른 컴파일러에서는 그렇지 않을 수 있습니다.

편집하다:

gcc 9.2.1로 테스트 한 후 printchar((char**)&test)실제로 test 값 캐스트로 전달되는 것으로 보입니다 char**. 지시 인 것처럼 때문이다 printchar((char**)test). 이 printchar함수 x에서 첫 번째 문자에 대한 이중 포인터가 아니라 배열 테스트의 첫 번째 문자에 대한 포인터입니다. 이중 역 참조 x는 배열의 첫 8 바이트가 유효한 주소에 해당하지 않기 때문에 세그먼트 화 오류가 발생합니다.

clang 9.0.0-2로 프로그램을 컴파일 할 때 정확히 동일한 동작과 결과를 얻습니다.

이것은 컴파일러 버그 또는 결과가 컴파일러에 따라 다를 수있는 정의되지 않은 동작의 결과로 간주 될 수 있습니다.

또 다른 예기치 않은 동작은 코드가

void printchar2(char (*x)[256]) {
    printf("px: %p\n", *x);
    printf("x: %p\n", x);
    printf("c: %c\n", **x);
}

출력은

px: 0x7ffd92627370
x: 0x7ffd92627370
c: A

이상한 행동은 그 x*x같은 가치를 가지고 있습니다.

이것은 컴파일러입니다. 나는 이것이 언어로 정의되어 있는지 의심합니다.