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


139

CI에 쓰는 경우 :

int num;

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


4
음, 선언 변수 가 아닌 정의 된 변수가 아닙니까? (내 C ++가 빛을 발하는 경우 미안합니다 ...)
sbi

6
아니요. 변수를 정의하지 않고 선언 할 수 있습니다. extern int x;그러나 정의는 항상 선언을 의미합니다. 선언이 클래스 정의에 있어야하고 선언이 클래스 정의 외부에 있어야하므로 선언하지 않고 정의 할 수있는 정적 클래스 멤버 변수가있는 C ++에서는 그렇지 않습니다.
bdonlan

ee.hawaii.edu/~tep/EE160/Book/chap14/subsection2.1.1.4.html 정의 된 것처럼 초기화해야합니다.
atp

답변:


188

정적 변수 (파일 범위 및 함수 정적)는 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 예외가 발생할 수 있습니다 . 그리고 변수를 초기화하지 않은 채로두면 컴파일러가 플래그 비트가 설정된 임의의 가비지를 가져올 수 있습니다. 즉 초기화되지 않은 변수를 만지면 치명적일 수 있습니다.


2
아뇨. 운이 좋으면, 고객 앞에 있지 않을 때 R 모드로 몇 달 동안 디버그 모드 일 수 있습니다.
Martin Beckett

8
무엇입니까? 정적 초기화는 표준에 필요합니다. ISO / IEC 9899 : 1999 참조 6.7.8 # 10
bdonlan

2
첫 번째 예는 내가 알 수있는 한 괜찮습니다. 컴파일러는 두 번째에 충돌 할 수 있습니다 내가 왜에 덜 해요 :)하지만

6
@Stuart : "트랩 표현"이라는 것이 있는데 이것은 기본적으로 유효한 값을 나타내지 않는 비트 패턴이며 예를 들어 런타임에 하드웨어 예외를 일으킬 수 있습니다. 모든 비트 패턴이 유효한 값임을 보장하는 유일한 C 유형은 다음과 같습니다 char. 다른 모든 것들은 트랩 표현을 가질 수 있습니다. 또는 초기화되지 않은 변수에 액세스하는 것이 어쨌든 UB이기 때문에 적합한 컴파일러는 단순히 검사를 수행하고 문제를 알리기로 결정할 수 있습니다.
Pavel Minaev

5
bdonian이 맞습니다. C는 항상 상당히 정확하게 지정되었습니다. C89와 C99 이전에는 dmr의 논문이 1970 년대 초에이 모든 것을 명시했다. 가장 조잡한 임베디드 시스템에서도 제대로 작동하려면 하나의 memset () 만 있으면되므로 부적합한 환경에 대한 변명이 없습니다. 나는 대답에서 표준을 인용했다.
DigitalRoss

57

정적 또는 전역 인 경우 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을 참조하십시오.


3
일반적으로 불확정 (사용 된?)은 무엇이든 할 수 있음을 의미합니다. 0이 될 수 있고, 그 안에 있던 값일 수 있으며, 프로그램을 중단시킬 수 있으며, 컴퓨터가 CD 슬롯에서 블루 베리 팬케이크를 생산할 수 있습니다. 당신은 절대적으로 보장하지 않습니다. 지구가 파괴 될 수 있습니다. 적어도 사양에 관한 한 ... 실제로 이와 같은 컴파일러를 만든 사람이라면 누구나 B-)에 크게 찌푸리게 될 것입니다.
Brian Postow

C11 N1570 초안에서 정의는 indeterminate value3.19.2에서 찾을 수 있습니다.
user3528438

정적 변수에 대해 어떤 값을 설정하는지 컴파일러 또는 OS에 항상 의존하도록합니까? 예를 들어 누군가가 내 자신의 OS 또는 컴파일러를 작성하고 기본적으로 정적 값에 대해 초기 값을 불확정으로 설정하면 가능합니까?
Aditya Singh

1
@AdityaSingh, OS는 컴파일러에서 더 쉽게 만들 수 있지만 궁극적으로 세계의 기존 C 코드 카탈로그를 실행하는 것은 컴파일러의 주요 책임이며 표준을 준수하는 2 차 책임입니다. 확실히 다르게 수는 있지만 왜 그렇습니까? OS가 있기 때문에 또한, 정적 데이터 확정을 할 까다로운 정말 보안상의 이유로 먼저 페이지를 제로로합니다. (자체 프로그램은 일반적으로 이전에 스택 주소를 사용했기 때문에 자동 변수는 피상적으로 예측할 수 없습니다.)
DigitalRoss

@BrianPostow 아니오, 맞지 않습니다. stackoverflow.com/a/40674888/584518을 참조하십시오 . 불확정 값을 사용하면됩니다 지정되지 않은 트랩 표현의 경우에 저장 동작하지 정의되지 않은 동작을.
룬딘

12

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

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

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

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

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


나는 이해할 수 없다 (물론 내가 잘 할 수있는 D :이 단지 분 후 DigitalRoss에서 한 것보다 훨씬 적은 upvotes을 가지고 왜)
안티 Haapala

7

우분투 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 커널에 달려 있습니다.


4

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


1

컴퓨터의 저장 용량은 한정되어 있기 때문에 자동 변수는 일반적으로 이전에 다른 임의의 목적으로 사용되었던 저장 요소 (레지스터 또는 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"의 반환 값이 무시된다는 사실에 신경 쓰지 않습니다. 반환하려고하면 임의의 코드를 생성 할 수있는 무제한 라이센스가 컴파일러에 부여됩니다.


0

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

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


0

스토리지 클래스가 정적 또는 글로벌 인 경우로드하는 동안 변수에 초기 값이 지정되지 않은 경우 BSS 는 변수 또는 메모리 위치 (ML)를 0으로 초기화 합니다. 로컬 초기화되지 않은 변수의 경우 트랩 표현이 메모리 위치에 지정됩니다. 따라서 중요한 정보가 포함 된 레지스터를 컴파일러가 덮어 쓰면 프로그램이 중단 될 수 있습니다.

그러나 일부 컴파일러에는 이러한 문제를 피하기위한 메커니즘이있을 수 있습니다.

char을 제외하고 데이터 유형에 대해 정의되지 않은 값을 나타내는 비트 패턴이있는 트랩 표현이 있음을 깨달았을 때 nec v850 시리즈로 작업하고있었습니다. 초기화되지 않은 문자를 가져 왔을 때 트랩 표현으로 인해 기본값이 0입니다. 이것은 necv850es를 사용하는 모든 사람에게 유용 할 수 있습니다


부호없는 문자를 사용할 때 트랩 표현을 얻으면 시스템이 호환되지 않는 것입니다. 트랩 표현, C17 6.2.6.1/5를 명시 적으로 포함 할 수 없습니다.
룬딘

-2

num 값은 메인 메모리 (RAM)의 가비지 값입니다. 생성 직후 변수를 초기화하면 더 좋습니다.


-4

내가 간 한 그것은 대부분 컴파일러에 달려 있지만 일반적으로 대부분의 경우 값은 compliers에 의해 0으로 가정됩니다.
TC ++의 경우 가비지 값을 얻었지만 TC는 0으로 값을주었습니다.

int i;
printf('%d',i);

예를 들어 결정적 값을 얻는 경우 0컴파일러는 변수를 초기화하는 코드를 추가하여 해당 값을 얻도록 추가 단계를 수행 할 가능성이 높습니다. 일부 컴파일러는 "디버그"컴파일을 수행 할 때이 작업을 수행하지만이 값 0을 선택하면 코드의 결함을 숨길 수 있기 때문에 나쁜 생각입니다 (보다 적절한 것은 0xBAADF00D유사 하지 않은 숫자를 보장 할 것입니다 ). 나는 대부분의 컴파일러가 메모리를 차지하기 위해 발생하는 쓰레기를 변수의 값으로 남겨 둘 것이라고 생각합니다 (즉, 일반적으로로 분류 되지 않음 0).
skyking
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.