programing

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

bestprogram 2023. 10. 19. 22:40

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

아래 코드를 확인해주시기 바랍니다.배열을 다음과 같이 전달하려고 합니다.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는 배열에 대한 포인터입니다.포인터에 대한 포인터가 아닙니다.

배열이 포인터라는 말을 들었을 수 있지만 이는 올바르지 않습니다.배열의 이름은 전체 개체(모든 요소)의 이름입니다.첫 번째 요소에 대한 포인터가 아닙니다.대부분의 식에서 배열은 첫 번째 요소의 포인터로 자동 변환됩니다.그것은 종종 유용한 편리함입니다.그러나 이 규칙에는 세 가지 예외가 있습니다.

  • 배열은 다음의 피연산자입니다.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, 주소를 취할 수 있는 포인터가 없으므로, 이것은 a를 생성할 수 없습니다.char **. A를 만들기 위해서는char **, 당신은 먼저 그것을 가지고 있어야 합니다.char *.

왜냐면test는 포인터가 아닙니다.

&test배열에 대한 포인터를 가져옵니다. 유형은 다음과 같습니다.char (*)[256], 와 호환되지 않는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);

그건 분명히 잘못된 겁니다

코드에서 인수가 예상됩니다.xprintchar가 들어있는 기억을 가리키다(char *).

첫 번째 통화에서 사용되는 스토리지를 가리킵니다.test2따라서 실제로 a를 가리키는 값입니다.(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같은 가치를 가지다

이것은 컴파일러입니다.나는 이것이 언어에 의해 정의되는지 의심스럽습니다.

언급URL : https://stackoverflow.com/questions/60016986/why-cant-i-access-a-pointer-to-pointer-for-a-stack-array