programing

범위를 벗어난 문자* 참조

css3 2023. 8. 8. 21:45

범위를 벗어난 문자* 참조

얼마 전에 C++로 프로그래밍하다가 최근에 다시 C에서 프로그래밍을 시작했는데, 포인터에 대한 이해가 좀 서툴러요.

이 코드로 인해 오류가 발생하지 않는 이유를 묻고 싶습니다.

char* a = NULL;
{
    char* b = "stackoverflow";
    a = b;
}

puts(a);

제가 생각한 이유는b벗어났습니다.a하지 않는 위치를 , 위치는 존하지않메위참치하조야므됩을 호출할 때 .printf.

저는 이 코드를 MSVC에서 20번 정도 실행했는데 오류가 나타나지 않았습니다.

【例내】가 있는 범위 b는 정의되며, 문자열 리터럴의 주소가 할당됩니다.이러한 리터럴은 일반적으로 스택이 아닌 메모리의 읽기 전용 섹션에서 사용됩니다.

을 할 때는.a=b을 할당합니다.ba를 들어, 예를 들어, 예를 들어,a이제 문자열 리터럴의 주소를 포함합니다.이 주소는 다음 이후에도 유효합니다.b범위를 벗어납니다.

만약 당신이 주소를 가져갔다면,b그런 다음 해당 주소의 참조를 취소하려고 시도하면 정의되지 않은 동작을 호출하게 됩니다.

따라서 코드가 유효하고 정의되지 않은 동작을 호출하지 않지만 다음과 같습니다.

int *a = NULL;
{
    int b = 6;
    a = &b;
}

printf("b=%d\n", *a);

더 미묘한 또 다른 예는 다음과 같습니다.

char *a = NULL;
{
    char b[] = "stackoverflow";
    a = b;
}

printf(a);

이 예와 당신의 예의 차이점은b이것은 배열이며, 할당될 때 첫 번째 요소에 대한 포인터로 붕괴합니다.a 이 에는 ㅠㅠㅠㅠㅠㅠㅠㅠㅠa범위를 벗어나는 로컬 변수의 주소를 포함합니다.

편집:

참고로, 변수를 첫 번째 인수로 통과시키는 것은 나쁜 관행입니다.printf형식 문자열 취약성으로 이어질 수 있습니다.다음과 같이 문자열 상수를 사용하는 것이 좋습니다.

printf("%s", a);

또는 더 간단하게:

puts(a);

다음은 코드가 수행하는 작업입니다.

char* a = NULL;

a는 아무것도하지 않는 입니다(「 」 「 」 「 」 「 」 「 」로 됩니다.NULL).

{
    char* b = "stackoverflow";

b 리터럴 ▁the▁▁pointer▁a▁literal를 참조하는 포인터입니다."stackoverflow".

    a = b;

a정적인 리터럴 " " " 를 됩니다."stackoverflow".

}

b범위를 벗어났습니다.하지만 그 이후로a참조하지 않음b그러면 그것은 중요하지 않습니다(그것은 단지 동일한 정적, 상수 문자열 리터럴을 참조하는 것입니다).b참조).

printf(a);

리터럴 " " " " 을 합니다."stackoverflow"에 의해 언급된.a.

문자열 리터럴은 정적으로 할당되므로 포인터는 무기한으로 유효합니다. 당신이 면다말했라고 .char b[] = "stackoverflow"그러면 스택에 범위가 종료되면 유효하지 않게 되는 문자 배열을 할당하게 됩니다.문자열을 .char s[] = "foo"스택은 사용자가 수정할 수 있는 문자열을 할당하는 반면,char *s = "foo"읽기 전용 메모리에 배치할 수 있는 문자열에 대한 포인터만 제공하므로 이를 수정하는 것은 정의되지 않은 동작입니다.

다른 사람들은 이 코드가 완벽하게 유효하다고 설명했습니다.이 대답은 코드가 유효하지 않았다면 호출할 때 런타임 오류가 발생했을 것이라는 당신의 예상에 대한 것입니다.printf꼭 그렇지는 않습니다.

잘못된 코드에 대한 이 변형을 살펴보겠습니다.

#include <stdio.h>
int main(void)
{
    int *a;
    {
        int b = 42;
        a = &b;
    }
    printf("%d\n", *a); // undefined behavior
    return 0;
}

이 프로그램은 정의되지 않은 동작을 가지고 있지만 실제로는 몇 가지 다른 이유로 42번을 인쇄할 가능성이 상당히 높습니다. 많은 컴파일러들이 다음을 위해 스택 슬롯을 떠날 것입니다.b의전를위할된당의 main에 남아 수 , 까지 아무 입니다. 컴파일러가 공식적으로 스택 슬롯의 할당을 해제했다고 해도, 42라는 숫자는 아마도 다른 것이 덮어쓸 때까지 메모리에 남아 있을 것이고, 그 사이에는 아무것도 없습니다.a = &b그리고.*a위해 및 전파하면 두 변수를 을 " 이위해는, ("전파복사및""에 쓸 수 있습니다.*a에직접에 printf이 진서당쓴 (것럼이처술신당)럼것쓴(▁if▁you▁(처)을 쓴 것처럼)printf("%d\n", 42)).

"정의되지 않은 행동"이 "프로그램이 예측 가능하게 충돌할 것"을 의미하지 않는다는 것을 이해하는 것이 절대적으로 중요합니다.이것은 "무엇이든 일어날 수 있다"는 의미이며, 프로그래머가 의도한 대로 작동하는 것처럼 보이는 것을 포함합니다( 컴퓨터에서, 이 컴파일러로, 오늘날).


마지막으로, 제가 편리하게 액세스할 수 있는 공격적인 디버깅 도구(Valgrind, ASan, UBSan)는 이 오류를 트랩할 만큼 "자동" 가변 수명을 충분히 자세히 추적하지 않지만 GCC 6은 다음과 같은 재미있는 경고를 생성합니다.

$ gcc -std=c11 -O2 -W -Wall -pedantic test.c
test.c: In function ‘main’:
test.c:9:5: warning: ‘b’ is used uninitialized in this function
    printf("%d\n", *a); // undefined behavior
    ^~~~~~~~~~~~~~~~~~

여기서 일어난 일은 위에서 설명한 최적화를 수행했습니다. 즉, 마지막으로 알려진 값을 복사하는 것입니다.b안으로*a에 고나안으로서그리▁the으로▁and안▁then▁into서.printf " 만지마으막알려진은값로지하"에 값b42가 이되지 않은" (그 후 42에 한다.)printf("%d\n", 0).)

문자열 리터럴은 항상 정적으로 할당되며 프로그램은 언제든지 액세스할 수 있습니다.

char* a = NULL;
{
    char* b = "stackoverflow";
    a = b;
}

printf(a);

여기 메모리를 문자열 리터럴로"stackoverflow"하게 컴파일러가 합니다.int/char변수 또는 포인터.

문자열 리터럴이 읽기 전용 섹션/세그먼트의 위치라는 점이 있습니다.b스택에 할당되었지만 읽기 전용 섹션/세그먼트의 메모리 주소를 보유하고 있습니다.

에서 var 드바서에b문자열 리터럴의 주소가 있습니다. 어는심 때도.b문자열 리터럴에 대한 메모리가 항상 할당될 범위를 풉니다.

참고: 문자열 리터럴에 할당된 메모리는 바이너리의 일부이며 프로그램이 언로드되면 제거됩니다.

자세한 내용은 ELF 이진 사양을 참조하십시오.

이 단순히 문자 포인터를 하는 것이기 b 포인터로a그리고 그것은 완벽하게 좋습니다.

C는 "는 "stackoverflow"됩니다.a변수.

변범벗지만 .b하지만 여전히 과제는 그와 함께 완료되었습니다.a입니다.그래서 그것은 오류 없이 결과를 인쇄할 것입니다.

이전 답변의 증거로, 코드 안에 실제로 무엇이 있는지 살펴보는 것이 좋다고 생각합니다.사람들은 문자열 리터럴이 .text 섹션 안에 있다고 이미 언급했습니다.그래서, 그들(문인)은 단순히, 항상, 거기에 있습니다.코드를 쉽게 찾을 수 있습니다.

#include <string.h>

int main() {
  char* a = 0;
  {
    char* b = "stackoverflow";
    a = c;
  }
  printf("%s\n", a);
}

다음 명령 사용

> cc -S main.c

맨 아래에 있는 main.s 내부.

...
...
...
        .section        __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
        .asciz  "stackoverflow"

L_.str.1:                               ## @.str.1
        .asciz  "%s\n"

어셈블리 섹션(예: https://docs.oracle.com/cd/E19455-01/806-3773/elf-3/index.html )에 대한 자세한 내용은 여기를 참조하십시오.

https://www.objc.io/issues/6-build-tools/mach-o-executables/ 에서 Mach-O 실행 파일에 대해 매우 잘 준비된 내용을 확인할 수 있습니다.

언급URL : https://stackoverflow.com/questions/44529617/referencing-a-char-that-went-out-of-scope