[c] C에서 선언되고 초기화되지 않은 변수는 어떻게됩니까? 가치가 있습니까?

CI에 쓰는 경우 :

int num;

에 무언가를 할당하기 전에 미정 num의 가치는 num무엇입니까?



답변

정적 변수 (파일 범위 및 함수 정적)는 0으로 초기화됩니다.

int x; // zero
int y = 0; // also zero

void foo() {
    static int x; // also zero
}

비 정적 변수 (로컬 변수)는 미정 입니다. 값을 할당하기 전에 값을 읽으면 정의되지 않은 동작이 발생합니다.

void foo() {
    int x;
    printf("%d", x); // the compiler is free to crash here
}

실제로, 그들은 초기에 무의미한 가치가있는 경향이 있습니다. 일부 컴파일러는 디버거를 볼 때 명확하게하기 위해 특정 고정 값을 넣을 수도 있습니다. 그러나 엄밀히 말하면 컴파일러는 충돌에서 소환까지 자유롭게 할 수 있습니다 당신의 코 통로를 통해 악마 .

단순히 “정의되지 않은 / 임의 값”이 아닌 정의되지 않은 동작 인 이유에 대해서는 다양한 유형에 대한 추가 플래그 비트가있는 여러 CPU 아키텍처가 있습니다. 현대의 예로는 Itanium이 있는데 레지스터에 “Not Thing”비트가 있습니다 . 물론 C 표준 제도자는 일부 오래된 아키텍처를 고려하고있었습니다.

이러한 플래그 비트가 설정된 값으로 작업을 시도하면 실제로 실패하지 않아야 하는 작업 (예 : 정수 추가 또는 다른 변수에 할당) 에서 CPU 예외가 발생할 수 있습니다 . 그리고 변수를 초기화하지 않은 채로두면 컴파일러가 플래그 비트가 설정된 임의의 가비지를 가져올 수 있습니다. 즉 초기화되지 않은 변수를 만지면 치명적일 수 있습니다.


답변

정적 또는 전역 인 경우 0, 스토리지 클래스가 자동 인 경우 미정

C는 항상 객체의 초기 값에 대해 매우 구체적이었습니다. global 또는 static인 경우 0이됩니다. 인 경우 auto값이 결정되지 않습니다 .

이는 C89 이전 컴파일러의 경우였으며 K & R과 DMR의 원래 C 보고서에 명시되어 있습니다.

이것은 C89의 경우입니다 (섹션 6.5.7 초기화 참조) .

자동 저장 기간이있는 개체가 명시 적으로 초기화되지 않으면 해당 값이 결정되지 않습니다. 정적 저장 기간이있는 객체가 명시 적으로 초기화되지 않은 경우, 산술 유형이있는 모든 멤버에 0이 할당되고 포인터 유형이있는 모든 멤버에 null 포인터 상수가 할당 된 것처럼 암시 적으로 초기화됩니다.

이것은 C99의 경우입니다 (섹션 6.7.8 초기화 참조) .

자동 저장 기간이있는 개체가 명시 적으로 초기화되지 않으면 해당 값이 결정되지 않습니다. 정적 저장 기간이있는 객체가 명시 적으로 초기화되지 않은
경우 — 포인터 유형이있는 경우 null 포인터로 초기화됩니다.
— 산술 유형 인 경우 0으로 초기화됩니다 (양수 또는 부호 없음).
— 집계 된 경우 모든 구성원은 이러한 규칙에 따라 (재귀 적으로) 초기화됩니다.
— 공용체 인 경우 첫 번째 명명 된 멤버는 이러한 규칙에 따라 (재귀 적으로) 초기화됩니다.

정확히 불확실한 의미에 관해서 는 C89가 확실하지 않다고 C99는 말합니다.

3.17.2
불확정 값

불특정 값 또는 트랩 표현 중

그러나 표준에 관계없이 실제로는 각 스택 페이지가 실제로 0으로 시작되지만 프로그램이 auto스토리지 클래스 값을 볼 때 해당 스택 주소를 마지막으로 사용했을 때 자신의 프로그램에서 남은 모든 것을 볼 수 있습니다. 많이 할당하면auto 배열 결국 0으로 깔끔하게 시작됩니다.

왜 이런 식일까요? 다른 질문에 대한 답변은 https://stackoverflow.com/a/2091505/140740을 참조하십시오.


답변

변수의 저장 기간에 따라 다릅니다. 정적 저장 기간을 갖는 변수는 항상 암시 적으로 0으로 초기화됩니다.

자동 (로컬) 변수의 경우 초기화되지 않은 변수의 미정입니다 . 불확실한 가치는 무엇보다도 그 변수에서 “볼 수있는” “가치”가 예측할 수 없을뿐만 아니라 안정적 이라고 보장 할 수 없음을 의미합니다 . 예를 들어, 실제로 (즉, UB를 1 초간 무시)이 코드

int num;
int a = num;
int b = num;

그 변수를 보증하지 않습니다 ab동일한 값을 받게됩니다. 흥미롭게도, 이것은 일부 이론적 개념이 아니며, 이것은 최적화의 결과로 실제로 실제로 발생합니다.

따라서 일반적으로 “가비지가 메모리에 무엇이든지 초기화되어있다”라는 대중적인 대답은 원격으로는 정확하지 않습니다. 초기화되지 않은 변수의 행동은 변수의 다른 초기화 쓰레기.


답변

우분투 15.10, 커널 4.2.0, x86-64, GCC 5.2.1 예

충분한 표준, 구현을 살펴 봅시다 🙂

지역 변수

표준 : 정의되지 않은 동작.

구현 : 프로그램은 스택 공간을 할당하고 해당 주소로 아무것도 이동하지 않으므로 이전에 사용되었던 모든 것이 사용됩니다.

#include <stdio.h>
int main() {
    int i;
    printf("%d\n", i);
}

로 컴파일 :

gcc -O0 -std=c99 a.c

출력 :

0

다음으로 디 컴파일합니다 :

objdump -dr a.out

에:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       48 83 ec 10             sub    $0x10,%rsp
  40053e:       8b 45 fc                mov    -0x4(%rbp),%eax
  400541:       89 c6                   mov    %eax,%esi
  400543:       bf e4 05 40 00          mov    $0x4005e4,%edi
  400548:       b8 00 00 00 00          mov    $0x0,%eax
  40054d:       e8 be fe ff ff          callq  400410 <printf@plt>
  400552:       b8 00 00 00 00          mov    $0x0,%eax
  400557:       c9                      leaveq
  400558:       c3                      retq

x86-64 호출 규칙에 대한 지식에서 :

  • %rdi첫 번째 printf 인수이므로 "%d\n"주소 의 문자열0x4005e4

  • %rsi두 번째 printf 인수 i입니다.

    그것에서 유래 -0x4(%rbp)제 4 바이트 로컬 변수이다.

    이 시점 rbp에서 스택의 첫 번째 페이지에 커널이 할당되었으므로 해당 값을 이해하기 위해 커널 코드를 살펴보고 그것이 무엇을 설정하는지 알아볼 것입니다.

    커널은 프로세스가 종료 될 때 다른 프로세스에 재사용하기 전에 해당 메모리를 어떤 것으로 설정합니까? 그렇지 않은 경우 새 프로세스는 다른 완료된 프로그램의 메모리를 읽고 데이터를 유출 할 수 있습니다. 참조 : 지금까지 보안 위험 값을 초기화되지 않은 있습니까?

그런 다음 자체 스택 수정을 사용하여 다음과 같은 재미있는 내용을 작성할 수도 있습니다.

#include <assert.h>

int f() {
    int i = 13;
    return i;
}

int g() {
    int i;
    return i;
}

int main() {
    f();
    assert(g() == 13);
}

지역 변수 -O3

구현 분석 : gdb에서 <값 최적화 됨>의 의미는 무엇입니까?

글로벌 변수

표준 : 0

구현 : .bss섹션.

#include <stdio.h>
int i;
int main() {
    printf("%d\n", i);
}

gcc -00 -std=c99 a.c

컴파일 :

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       8b 05 04 0b 20 00       mov    0x200b04(%rip),%eax        # 601044 <i>
  400540:       89 c6                   mov    %eax,%esi
  400542:       bf e4 05 40 00          mov    $0x4005e4,%edi
  400547:       b8 00 00 00 00          mov    $0x0,%eax
  40054c:       e8 bf fe ff ff          callq  400410 <printf@plt>
  400551:       b8 00 00 00 00          mov    $0x0,%eax
  400556:       5d                      pop    %rbp
  400557:       c3                      retq
  400558:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40055f:       00

# 601044 <i>그 말한다 i주소입니다 0x601044및 :

readelf -SW a.out

포함한다 :

[25] .bss              NOBITS          0000000000601040 001040 000008 00  WA  0   0  4

어느 말했다 0x601044오른쪽의 중간에 .bss시작 섹션 0x6010408 바이트이다.

ELF 표준은 다음라는 이름의 섹션이 보장 .bss완전히 제로의 가득 :

.bss이 섹션에는 프로그램의 메모리 이미지에 기여하는 초기화되지 않은 데이터가 있습니다. 정의에 따라 시스템은 프로그램이 시작될 때 0으로 데이터를 초기화합니다. 섹션 유형으로 표시된대로 섹션이 파일 공간을 차지하지 않습니다 SHT_NOBITS.

또한 형식 SHT_NOBITS은 효율적이며 실행 파일에서 공간을 차지하지 않습니다.

sh_size이 멤버는 섹션의 크기를 바이트 단위로 제공합니다. 섹션 유형이 인 SHT_NOBITS경우를 제외하고 섹션은 sh_size
파일에서 바이트를 차지 합니다. 유형의 섹션은 SHT_NOBITS0이 아닌 크기를 가질 수 있지만 파일에서 공간을 차지하지 않습니다.

그런 다음 프로그램을 시작할 때 메모리에 프로그램을로드 할 때 해당 메모리 영역을 제로화하는 것은 Linux 커널에 달려 있습니다.


답변

조건에 따라서. 해당 정의가 전역 (모든 함수 외부)이면 num0으로 초기화됩니다. 함수 내부에 로컬 인 경우 해당 값이 결정되지 않습니다. 이론적으로 값을 읽으려고 시도해도 정의되지 않은 동작이 있습니다 .C는 값에 기여하지 않는 비트의 가능성을 허용하지만 변수를 읽음으로써 정의 된 결과를 얻도록 특정 방법으로 설정해야합니다.


답변

컴퓨터의 저장 용량은 한정되어 있기 때문에 자동 변수는 일반적으로 이전에 다른 임의의 목적으로 사용되었던 저장 요소 (레지스터 또는 RAM)에 보관됩니다. 이러한 변수가 값을 할당되기 전에 사용 된 경우 해당 스토리지는 이전에 보유한 모든 것을 보유 할 수 있으므로 변수의 내용을 예측할 수 없게됩니다.

추가 주름으로, 많은 컴파일러는 연관된 유형보다 큰 레지스터에 변수를 유지할 수 있습니다. 변수에 쓰여지고 다시 읽은 값이 잘 리거나 올바른 크기로 부호 확장되도록 컴파일러가 필요하지만, 많은 컴파일러는 변수가 쓰여질 때 이러한 잘림을 수행하여 변수를 읽기 전에 수행되었습니다. 이러한 컴파일러에서는 다음과 같은 것이 있습니다.

uint16_t hey(uint32_t x, uint32_t mode)
{ uint16_t q;
  if (mode==1) q=2;
  if (mode==3) q=4;
  return q; }

 uint32_t wow(uint32_t mode) {
   return hey(1234567, mode);
 }

잘 될 수있는 wow()각각의 레지스터 0과 1의 값으로 1234567를 저장하고 호출 foo(). 이후 x기능이 레지스터 0으로 자신의 반환 값을 넣어되어 있기 때문에 “foo는”내 필요하지 않고, 컴파일러는 0에 등록 할당 할 수 있습니다 q. 만약mode 그 값의 범위 내에 있지 않은 경우에도 1 또는 3이고, 0는 각각 2 또는 4로로드 될 레지스터하지만 다른 값이면,이 함수는 레지스터 0 (즉, 값 1234567)이었다대로 돌아갈 수도 uint16_t의

초기화되지 않은 변수가 도메인 외부에 값을 보유하지 않도록하기 위해 컴파일러가 추가 작업을 수행하지 않고 불확실한 동작을 지나치게 자세하게 지정할 필요가 없도록 표준에서는 초기화되지 않은 자동 변수를 사용하는 것이 정의되지 않은 동작이라고 말합니다. 경우에 따라이 값이 해당 유형의 범위를 벗어난 값보다 더 놀라운 결과 일 수 있습니다. 예를 들면 다음과 같습니다.

void moo(int mode)
{
  if (mode < 5)
    launch_nukes();
  hey(0, mode);
}

컴파일러는 moo()3보다 큰 모드로 호출 하면 필연적으로 정의되지 않은 동작을 호출하는 프로그램이 발생할 수 있으므로 컴파일러 mode는 4 이상인 경우에만 관련 이있는 코드 (예 : 일반적으로 방해하는 코드)를 생략 할 수 있습니다 그러한 경우 핵무기 발사. 표준이나 현대의 컴파일러 철학은 “hey”의 반환 값이 무시된다는 사실에 신경 쓰지 않습니다. 반환하려고하면 임의의 코드를 생성 할 수있는 무제한 라이센스가 컴파일러에 부여됩니다.


답변

기본적인 대답은 그렇습니다, 그것은 정의되지 않았습니다.

이로 인해 이상한 동작이 나타나는 경우 선언 된 위치에 따라 달라질 수 있습니다. 스택의 함수 내에서 함수가 호출 될 때마다 내용이 다를 수 있습니다. 정적 또는 모듈 범위이면 정의되지 않지만 변경되지 않습니다.