CI에 쓰는 경우 :
int num;
에 무언가를 할당하기 전에 미정 num
의 가치는 num
무엇입니까?
extern int x;
그러나 정의는 항상 선언을 의미합니다. 선언이 클래스 정의에 있어야하고 선언이 클래스 정의 외부에 있어야하므로 선언하지 않고 정의 할 수있는 정적 클래스 멤버 변수가있는 C ++에서는 그렇지 않습니다.
CI에 쓰는 경우 :
int num;
에 무언가를 할당하기 전에 미정 num
의 가치는 num
무엇입니까?
extern int x;
그러나 정의는 항상 선언을 의미합니다. 선언이 클래스 정의에 있어야하고 선언이 클래스 정의 외부에 있어야하므로 선언하지 않고 정의 할 수있는 정적 클래스 멤버 변수가있는 C ++에서는 그렇지 않습니다.
답변:
정적 변수 (파일 범위 및 함수 정적)는 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 예외가 발생할 수 있습니다 . 그리고 변수를 초기화하지 않은 채로두면 컴파일러가 플래그 비트가 설정된 임의의 가비지를 가져올 수 있습니다. 즉 초기화되지 않은 변수를 만지면 치명적일 수 있습니다.
char
. 다른 모든 것들은 트랩 표현을 가질 수 있습니다. 또는 초기화되지 않은 변수에 액세스하는 것이 어쨌든 UB이기 때문에 적합한 컴파일러는 단순히 검사를 수행하고 문제를 알리기로 결정할 수 있습니다.
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을 참조하십시오.
indeterminate value
3.19.2에서 찾을 수 있습니다.
변수의 저장 기간에 따라 다릅니다. 정적 저장 기간을 갖는 변수는 항상 암시 적으로 0으로 초기화됩니다.
자동 (로컬) 변수의 경우 초기화되지 않은 변수의 값 은 미정입니다 . 불확실한 가치는 무엇보다도 그 변수에서 "볼 수있는" "가치"가 예측할 수 없을뿐만 아니라 안정적 이라고 보장 할 수 없음을 의미합니다 . 예를 들어, 실제로 (즉, UB를 1 초간 무시)이 코드
int num;
int a = num;
int b = num;
그 변수를 보증하지 않습니다 a
와 b
동일한 값을 받게됩니다. 흥미롭게도, 이것은 일부 이론적 개념이 아니며, 이것은 최적화의 결과로 실제로 실제로 발생합니다.
따라서 일반적으로 "가비지가 메모리에 무엇이든지 초기화되어있다"라는 대중적인 대답은 원격으로는 정확하지 않습니다. 초기화되지 않은 변수의 행동은 변수의 다른 초기화 쓰레기.
우분투 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
시작 섹션 0x601040
8 바이트이다.
ELF 표준은 다음라는 이름의 섹션이 보장 .bss
완전히 제로의 가득 :
.bss
이 섹션에는 프로그램의 메모리 이미지에 기여하는 초기화되지 않은 데이터가 있습니다. 정의에 따라 시스템은 프로그램이 시작될 때 0으로 데이터를 초기화합니다. 섹션 유형으로 표시된대로 섹션이 파일 공간을 차지하지 않습니다SHT_NOBITS
.
또한 형식 SHT_NOBITS
은 효율적이며 실행 파일에서 공간을 차지하지 않습니다.
sh_size
이 멤버는 섹션의 크기를 바이트 단위로 제공합니다. 섹션 유형이 인SHT_NOBITS
경우를 제외하고 섹션은sh_size
파일에서 바이트를 차지 합니다. 유형의 섹션은SHT_NOBITS
0이 아닌 크기를 가질 수 있지만 파일에서 공간을 차지하지 않습니다.
그런 다음 프로그램을 시작할 때 메모리에 프로그램을로드 할 때 해당 메모리 영역을 제로화하는 것은 Linux 커널에 달려 있습니다.
컴퓨터의 저장 용량은 한정되어 있기 때문에 자동 변수는 일반적으로 이전에 다른 임의의 목적으로 사용되었던 저장 요소 (레지스터 또는 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"의 반환 값이 무시된다는 사실에 신경 쓰지 않습니다. 반환하려고하면 임의의 코드를 생성 할 수있는 무제한 라이센스가 컴파일러에 부여됩니다.
스토리지 클래스가 정적 또는 글로벌 인 경우로드하는 동안 변수에 초기 값이 지정되지 않은 경우 BSS 는 변수 또는 메모리 위치 (ML)를 0으로 초기화 합니다. 로컬 초기화되지 않은 변수의 경우 트랩 표현이 메모리 위치에 지정됩니다. 따라서 중요한 정보가 포함 된 레지스터를 컴파일러가 덮어 쓰면 프로그램이 중단 될 수 있습니다.
그러나 일부 컴파일러에는 이러한 문제를 피하기위한 메커니즘이있을 수 있습니다.
char을 제외하고 데이터 유형에 대해 정의되지 않은 값을 나타내는 비트 패턴이있는 트랩 표현이 있음을 깨달았을 때 nec v850 시리즈로 작업하고있었습니다. 초기화되지 않은 문자를 가져 왔을 때 트랩 표현으로 인해 기본값이 0입니다. 이것은 necv850es를 사용하는 모든 사람에게 유용 할 수 있습니다
내가 간 한 그것은 대부분 컴파일러에 달려 있지만 일반적으로 대부분의 경우 값은 compliers에 의해 0으로 가정됩니다.
TC ++의 경우 가비지 값을 얻었지만 TC는 0으로 값을주었습니다.
int i;
printf('%d',i);
0
컴파일러는 변수를 초기화하는 코드를 추가하여 해당 값을 얻도록 추가 단계를 수행 할 가능성이 높습니다. 일부 컴파일러는 "디버그"컴파일을 수행 할 때이 작업을 수행하지만이 값 0
을 선택하면 코드의 결함을 숨길 수 있기 때문에 나쁜 생각입니다 (보다 적절한 것은 0xBAADF00D
유사 하지 않은 숫자를 보장 할 것입니다 ). 나는 대부분의 컴파일러가 메모리를 차지하기 위해 발생하는 쓰레기를 변수의 값으로 남겨 둘 것이라고 생각합니다 (즉, 일반적으로로 분류 되지 않음 0
).