C에서 선언된 미초기화 변수는 어떻게 됩니까?가치가 있나요?
C에 쓸 경우:
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이 있습니다.Itanium은 레지스터에 "Not a Thing"이라는 비트가 있습니다.물론 C 표준 드래프트 작성자는 오래된 아키텍처를 고려하고 있었습니다.
이러한 플래그 비트가 설정된 값으로 작업하려고 하면 실제로 실패해서는 안 되는 작업(정수 추가 또는 다른 변수에 할당 등)에서 CPU 예외가 발생할 수 있습니다.변수를 초기화하지 않은 상태로 두면 컴파일러는 이러한 플래그 비트가 설정된 임의의 가비지를 검출할 수 있습니다.즉, 초기화되지 않은 변수를 터치하는 것은 치명적일 수 있습니다.
0(고정 또는 글로벌인 경우), 스토리지 클래스가 자동인지 여부를 알 수 없습니다.
C는 항상 객체의 초기값에 대해 매우 구체적입니다. 또는 「」의 static0이 되다. 만약이다.auto값은 미확정입니다.
이는 C89 이전 컴파일러의 경우로 K&R과 DMR의 원본 C 보고서에 명시되어 있습니다.
이는 C89의 경우로 섹션 6.5.7 초기화를 참조하십시오.
저장 기간이 자동인 객체가 명확하게 초기화되지 않은 경우 그 값은 불확실합니다.정적 저장 기간을 가진 객체가 명확하게 초기화되지 않은 경우, 연산 유형을 가진 모든 멤버에 0이 할당되고 포인터 유형을 가진 모든 멤버에 늘 포인터 상수가 할당되는 것처럼 암시적으로 초기화됩니다.
이는 C99의 경우로 섹션 6.7.8 초기화를 참조하십시오.
저장 기간이 자동인 개체가 명시적으로 초기화되지 않은 경우 해당 값은 미확정입니다.을 가지는 되어 있지 않은 .
: 타입이 , 로 초기화됩니다. : " 는 Null 포인터로 초기화됩니다.
- 타입이 ( 또는 없음 산 、 양 、 으 、 0 。
에 따라
첫 유니언일 경우 첫 번째 명명된 멤버는 다음 규칙에 따라 (순차적으로) 초기화됩니다.
정확히 어떤 의미인지, C89는 확실하지 않다고 C99는 말합니다.
3.17.2
값 중
각제로로 는 0으로 합니다.auto스토리지 클래스 값. 프로그램이 스택 주소를 마지막으로 사용했을 때 남겨진 모든 것을 확인합니다. auto어레이는 최종적으로 0부터 깔끔하게 시작됩니다.
궁금할 수도 있어요, 왜 이런 식이죠?다른 SO 답변에서는 이 질문에 대해 설명합니다.https://stackoverflow.com/a/2091505/140740 를 참조해 주세요.
변수의 저장 기간에 따라 달라집니다.정적 저장 기간을 갖는 변수는 항상 암묵적으로 0으로 초기화됩니다.
자동(로컬) 변수에 대해서는 초기화되지 않은 변수의 값이 미확정입니다.특히 값이 부정하다는 것은 변수에서 "보이는" 어떤 값도 예측할 수 없을 뿐만 아니라 안정성이 보장되지 않는다는 것을 의미합니다.예를 들어, 실제로(즉, UB를 1초 동안 무시함) 이 코드는
int num;
int a = num;
int b = num;
가 반드시 한 것은 .a ★★★★★★★★★★★★★★★★★」b는 동일한 값을 받습니다.흥미롭게도, 이것은 현학적인 이론적인 개념이 아닙니다. 이것은 최적화의 결과로 실제로 쉽게 발생합니다.
따라서 일반적으로 "메모리에 있던 가비지로 초기화되었습니다"라는 일반적인 답변은 전혀 정확하지 않습니다.초기화되지 않은 변수의 동작이 가비지로 초기화된 변수의 동작과 다릅니다.
Ubuntu 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는 커널에 의해 할당된 스택의 첫 페이지에 있습니다.따라서 이 값을 이해하기 위해 커널 코드를 조사하고 어떤 값으로 설정되어 있는지 알아보겠습니다.TODO는 프로세스가 정지했을 때 커널이 메모리를 다른 프로세스에 재사용하기 전에 메모리를 무엇으로 설정합니까?그렇지 않으면 새로운 프로세스는 다른 완료된 프로그램의 메모리를 읽을 수 있고 데이터가 유출됩니다.참조: 초기화되지 않은 값이 보안 리스크가 되는 경우가 있습니까?
그 후, 독자적인 스택의 변경에 의해서, 다음과 같은 재미있는 내용을 작성할 수도 있습니다.
#include <assert.h>
int f() {
int i = 13;
return i;
}
int g() {
int i;
return i;
}
int main() {
f();
assert(g() == 13);
}
GCC 11은 다른 어셈블리 출력을 생성하는 것처럼 보이며, 위의 코드는 "작동"을 중지합니다.결국 정의되지 않은 동작입니다.gcc의 -O3는 로컬 변수를 0으로 초기화하지만 -O0은 초기화하지 않는 이유는 무엇입니까?
의 로컬 -O3
구현 분석: <value optimized out>은 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이 멤버는 섹션의 크기를 바이트 단위로 제공합니다., 분할 이 "Drughter"가 아닌SHT_NOBITS은 점유하고 있습니다.sh_size바이트 수를 지정합니다. 「」의 .SHT_NOBITS0이 아닌 크기를 가질 수 있지만 파일에는 공간을 차지하지 않습니다.
그 후 프로그램을 시작할 때 메모리에 로딩할 때 메모리 영역을 0으로 만드는 것은 Linux 커널에 달려 있습니다.
제가 알기론 대부분 컴파일러에 의존합니다만, 대부분의 경우 이 값은 컴파일러에 의해 0으로 간주됩니다.
나는 VC++의 경우 가비지 값을 얻었고 TC는 값을 0으로 지정했습니다.와 같이 합니다.
int i;
printf('%d',i);
기본적인 대답은 정의되지 않았다는 것입니다.
이 때문에 이상한 동작이 발생하는 경우는, 그것이 선언되어 있는 장소에 따라서 다를 수 있습니다.스택상의 함수내에 있는 경우는, 함수가 호출될 때마다, 그 내용이 거의 다릅니다.정적 또는 모듈 스코프인 경우 정의되지 않았지만 변경되지 않습니다.
우우 르함수의 ), 「」( 「」)num으로 됩니다.로로초초 초다다다다다로컬(함수 내부)인 경우 값은 미확정입니다.이론적으로는 값을 읽으려고 해도 동작이 정의되지 않습니다.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()과 레지스터1에하고, 콜링 "1234567" "0" "1" "1" ""foo() .부터x내에서는 을 "foo"에 할당할 수 .q.mode1번, 3번, 1면 3면 된다.레지스터 0은 각각2 또는 4로 로드됩니다만, 다른 값인 경우, 그 값이 uint16_t 의 범위에 포함되지 않아도, 레지스터 0 에 있던 것(즉, 1234567)을 반환할 수 있습니다.
초기화되지 않은 변수가 영역 밖의 가치를 보유하는 것처럼 보이지 않도록 컴파일러가 추가 작업을 수행하도록 하는 것을 피하고, 미초기화 자동변수의 사용은 정의되지 않은 행동이라고 이 기준서는 밝히고 있다.경우에 따라서는, 이러한 유형의 범위를 벗어나는 값보다 결과가 훨씬 더 놀랄 수 있다.예를 들어 다음과 같습니다.
void moo(int mode)
{
if (mode < 5)
launch_nukes();
hey(0, mode);
}
는 그것을 할 수 .왜냐하면moo()보다 클 경우 않은 는 3이 정의되지 않은 동작을 호출하는 경우에만 할 수 .컴파일러는 다음과 같은 경우에만 관련된 코드를 생략할 수 있습니다.mode예를 들어, 이러한 경우 일반적으로 핵 발사를 막는 코드와 같이 4 이상입니다.스탠다드나 현대의 컴파일러 철학 중 어느 것도 "hey"로부터의 반환값이 무시된다는 사실에 신경 쓰지 않는다는 점에 유의하십시오. 반환을 시도하는 행위는 컴파일러에게 임의 코드를 생성하기 위한 무제한 라이센스를 부여합니다.
스토리지 클래스가 스태틱 또는 글로벌일 경우 로드 중에 BSS는 변수가 처음에 어떤 값을 할당받지 않는 한 변수 또는 메모리 위치(ML)를 0으로 초기화합니다.로컬 미초기화 변수의 경우 트랩 표현은 메모리 위치에 할당됩니다.따라서 중요한 정보를 포함하는 레지스터 중 하나가 컴파일러에 의해 덮어쓰게 되면 프로그램이 크래시 될 수 있습니다.
그러나 일부 컴파일러는 이러한 문제를 피하기 위한 메커니즘을 가지고 있을 수 있습니다.
nec v850 시리즈로 작업하고 있을 때 char 이외의 데이터 타입에 대해 정의되지 않은 값을 나타내는 비트 패턴이 있는 트랩 표현이 있음을 깨달았습니다.초기화되지 않은 문자를 사용했을 때 트랩 표현으로 인해 기본값이 0이 되었습니다.이는 necv850을 사용하는 any1에 도움이 될 수 있습니다.
언급URL : https://stackoverflow.com/questions/1597405/what-happens-to-a-declared-uninitialized-variable-in-c-does-it-have-a-value
'programing' 카테고리의 다른 글
| Vuejs - 이벤트 위임 및 컨텍스트 참조용 v- (0) | 2022.07.10 |
|---|---|
| VUEX의 변환으로 특정 상태 변환 (0) | 2022.07.10 |
| _DEBUG vs NDEBUG (0) | 2022.07.08 |
| vue 컴포넌트에 html 전달 (0) | 2022.07.08 |
| Vue 라우터가 vue CLI 빌드에서 작동하지 않음 (0) | 2022.07.08 |