(왜) 초기화되지 않은 변수를 사용하는 것은 정의되지 않은 동작입니까?
있는 경우:
unsigned int x;
x -= x;
는 것은 분명하다x
이 표현 뒤에 0이 되어야 하지만, 내가 보는 모든 곳에서, 그들은 이 코드의 동작이 정의되지 않았다고 말합니다, 단지 값의 정의뿐만 아니라x
(감산 전까지).
두 가지 질문:
이 코드의 동작은 정말 정의되어 있지 않은가?
(예: 호환 시스템에서 코드가 크래시(또는 더 악화)될 수 있습니까?)그렇다면 C는 왜 동작이 정의되어 있지 않다고 말하는가?
x
여기가 0이어야 하나요?즉, 여기서 동작을 정의하지 않음으로써 얻을 수 있는 이점은 무엇입니까?
분명히 컴파일러는 변수 내에서 "handy"라고 생각되는 가비지 값을 사용할 수 있으며, 의도한 대로 작동합니다.그 접근법이 뭐가 문제죠?
네, 이 동작은 정의되어 있지 않지만 대부분의 사람들이 알고 있는 것과는 다른 이유로 인해 정의되어 있습니다.
첫째, 유니터리화된 값을 사용하는 것 자체가 정의되지 않은 동작이 아니라 단순히 값이 결정되지 않은 것입니다.값이 유형에 대한 트랩 표현일 경우 이 값에 액세스하는 것은 UB입니다.부호 없는 타입은 트랩 표현이 거의 없기 때문에 비교적 안전합니다.
동작을 정의하지 못하게 하는 것은 변수의 추가 속성, 즉 "로 선언될 수 있음"입니다.register
그 주소는 절대 수신되지 않습니다.이러한 변수는 "초기화되지 않은" 유형의 추가 상태를 가진 실제 CPU 레지스터를 가진 아키텍처가 있기 때문에 특별히 취급됩니다.
편집: 이 표준의 관련 문구는 6.3.2.1p2이다.
lvalue가 레지스터 스토리지 클래스에서 선언할 수 있는 자동 스토리지 기간의 개체를 지정하고(주소를 가져간 적이 없음) 해당 개체가 초기화되지 않은 경우(Initializer를 사용하여 선언되지 않았으며 사용 전에 할당이 수행되지 않은 경우) 동작은 정의되지 않습니다.
더 명확하게 하기 위해 다음 코드는 모든 상황에서 합법입니다.
unsigned char a, b;
memcpy(&a, &b, 1);
a -= a;
- 여기 주소가 있습니다.
a
그리고.b
그래서 그 가치는 불확실합니다. - 부터
unsigned char
에는, 부정치가 특정되지 않은 트랩 표현은 없습니다.unsigned char
일어날 수 있어요 - 마지막에
a
값을 보유해야 합니다.0
.
편집 2: a
그리고.b
값이 지정되지 않았습니다.
3.19.3 미지정 값
이 국제표준이 어떤 경우에 어떤 값을 선택했는지에 대한 요건을 부과하지 않는 경우 관련 유형의 유효값
초기화되지 않았거나 기타 이유로 값이 미확정인 모든 유형의 변수에 대해 다음 사항이 해당 값을 코드 판독에 적용됩니다.
- 변수가 자동 저장 기간을 가지며 주소를 가져오지 않은 경우 코드는 항상 정의되지 않은 동작을 호출합니다 [1].
- 그렇지 않으면 시스템이 특정 변수 유형에 대해 트랩 표현을 지원하는 경우 코드는 항상 정의되지 않은 동작을 호출합니다 [2].
그렇지 않으면 트랩 표현이 없는 경우 변수는 지정되지 않은 값을 사용합니다.변수를 읽을 때마다 이 지정되지 않은 값이 일관된다는 보장은 없습니다.단, 트랩 표현이 아닌 것이 보증되므로 정의되지 않은 동작을 호출하지 않는 것이 보증됩니다[3].
이 값은 프로그램 크래시를 일으키지 않고 안전하게 사용할 수 있습니다.단, 이러한 코드는 트랩 표현을 가진 시스템에서는 이식할 수 없습니다.
[1]: C11 6.3.2.1:
lvalue가 레지스터 스토리지 클래스에서 선언할 수 있는 자동 스토리지 기간의 개체를 지정하고(주소를 가져간 적이 없음) 해당 개체가 초기화되지 않은 경우(Initializer를 사용하여 선언되지 않았으며 사용 전에 할당이 수행되지 않은 경우) 동작은 정의되지 않습니다.
[2]: C11 6.2.6.1:
특정 개체 표현은 개체 유형의 값을 나타낼 필요가 없습니다.객체의 저장된 값이 이러한 표현을 가지며 문자 유형이 없는 lvalue 식에 의해 읽혀질 경우 동작은 정의되지 않습니다.이러한 표현이 문자 유형이 없는 lvalue 식에 의해 객체의 전부 또는 일부를 수정하는 부작용에 의해 생성되는 경우 동작은 정의되지 않습니다.50). 이러한 표현을 트랩 표현이라고 합니다.
[3] C11:
3.19.2
불확정값
지정되지 않은 값 또는 트랩 표현 중 하나3.19.3
불특정값
이 국제표준이 어떤 경우에 어떤 값을 선택했는지에 대한 요건을 부과하지 않는 경우 관련 유형의 유효값
참고 지정되지 않은 값은 트랩 표현일 수 없습니다.3.19.4
트랩 표현
오브젝트 타입의 값을 나타낼 필요가 없는 오브젝트 표현
C 표준은 컴파일러가 최적화를 수행할 수 있는 많은 허용도를 제공합니다.이러한 최적화의 결과는 초기화되지 않은 메모리가 랜덤 비트 패턴으로 설정되고 모든 작업이 기록된 순서대로 수행되는 순진한 프로그램 모델을 가정하면 놀랄 수 있습니다.
주의: 다음 예는 다음 경우에만 유효합니다.x
주소를 취득하지 않기 때문에, 「등록과 같은」상태입니다.또, 다음과 같은 타입이 있는 경우에서도 유효합니다.x
는 트랩 표현을 가지고 있습니다.서명되지 않은 타입(최소한1비트의 스토리지를 필요로 하고 문서화해야 함)의 경우는 거의 없습니다.unsigned char
.한다면x
는 부호 있는 타입을 가지고 있기 때문에 실장에서는 -(2-1n-1)~2-1n-1 사이의 숫자가 아닌 비트패턴을 트랩 표현으로 정의할 수 있습니다.옌스 구스테트의 답을 보세요.
레지스터가 메모리보다 빠르기 때문에 컴파일러는 레지스터를 변수에 할당하려고 합니다.이 프로그램은 프로세서가 가진 레지스터보다 더 많은 변수를 사용할 수 있기 때문에 컴파일러는 레지스터 할당을 수행하며, 이는 다른 시간에 동일한 레지스터를 사용하는 다른 변수로 이어집니다.프로그램 단편 검토
unsigned x, y, z; /* 0 */
y = 0; /* 1 */
z = 4; /* 2 */
x = - x; /* 3 */
y = y + z; /* 4 */
x = y + 1; /* 5 */
3번 라인이 평가될 때,x
아직 초기화되지 않았기 때문에 (컴파일러를 제외한) 라인 3은 컴파일러가 충분히 스마트하지 않은 다른 조건 때문에 발생할 수 없는 일종의 요행일 것입니다.부터z
4행 이후에는 사용되지 않습니다.x
5행 앞에는 사용하지 않습니다.두 변수에 동일한 레지스터를 사용할 수 있습니다.따라서 이 작은 프로그램은 레지스터에 다음과 같은 연산을 위해 컴파일됩니다.
r1 = 0;
r0 = 4;
r0 = - r0;
r1 += r0;
r0 = r1;
최종 가치x
의 최종값입니다.r0
, 및 의 최종값y
의 최종값입니다.r1
이러한 값은 x = -3 및 y = -4이며, 다음과 같은 경우 5와 4가 아닙니다.x
올바르게 초기화되었습니다.
보다 상세한 예에서는, 다음의 코드 프래그먼트를 검토해 주세요.
unsigned i, x;
for (i = 0; i < 10; i++) {
x = (condition() ? some_value() : -x);
}
컴파일러가 다음을 검출했다고 가정합니다.condition
부작용이 없습니다.부터condition
수정하지 않다x
컴파일러는 루프를 통해 처음 실행되었을 때 액세스 할 수 없다는 것을 알고 있습니다.x
아직 초기화되지 않았기 때문에따라서 루프 본체의 첫 번째 실행은 다음과 같습니다.x = some_value()
상태를 테스트할 필요가 없습니다.컴파일러는 사용자가 작성한 것처럼 이 코드를 컴파일할 수 있습니다.
unsigned i, x;
i = 0; /* if some_value() uses i */
x = some_value();
for (i = 1; i < 10; i++) {
x = (condition() ? some_value() : -x);
}
컴파일러 내부에서 모델링되는 방법은 다음과 같은 값을 고려하는 것입니다.x
편리한 값어치가 있는 한x
초기화되지 않았습니다.변수가 단순히 지정되지 않은 값을 갖는 것이 아니라 초기화되지 않은 변수가 정의되지 않았을 때의 동작이므로 컴파일러는 편리한 값 사이의 특별한 수학적 관계를 추적할 필요가 없습니다.따라서 컴파일러는 위의 코드를 다음과 같이 분석할 수 있습니다.
- 첫 번째 루프 반복 중에
x
에 의해 초기화되지 않았습니다.-x
평가됩니다. -x
정의되지 않은 동작이 있기 때문에 그 값은 무엇이든 상관없습니다.- 최적화 규칙
condition ? value : value
이 코드를 간단하게 할 수 있도록condition; value
.
질문의 코드에 직면했을 때, 이 컴파일러는 다음과 같이 분석합니다.x = - x
평가되고, 그 가치는-x
무엇이든지 과학적이다.따라서 할당 작업을 최적화할 수 있습니다.
위에서 설명한 것과 같이 동작하는 컴파일러의 예를 찾지는 않았지만, 그것은 좋은 컴파일러가 하려고 하는 최적화입니다.나는 그것을 만나도 놀라지 않을 것이다.다음은 프로그램이 크래시되는 컴파일러의 덜 신뢰할 수 있는 예입니다. (어느 종류의 고급 디버깅 모드로 프로그램을 컴파일하는 경우 그렇게 불가능하지 않을 수 있습니다.
이 가상의 컴파일러는 다른 메모리 페이지에 있는 모든 변수를 매핑하고 초기화되지 않은 변수에서 읽으면 디버거를 호출하는 프로세서 트랩이 발생하도록 페이지 속성을 설정합니다.먼저 변수에 할당하면 해당 메모리 페이지가 정상적으로 매핑되는지 확인합니다.이 컴파일러는 고도의 최적화를 실행하려고 하지 않습니다.디버깅 모드이기 때문에 초기화되지 않은 변수 등의 버그를 쉽게 찾을 수 있습니다.언제x = - x
이 평가되고 우측에 트랩이 발생하고 디버거가 기동합니다.
네, 프로그램이 충돌할 수 있습니다.예를 들어 트랩 표현(처리할 수 없는 특정 비트 패턴)이 있을 수 있습니다.이 경우 CPU 인터럽트가 발생하여 처리하지 않으면 프로그램이 크래시 될 수 있습니다.
(최신 C11 드래프트의 6.2.6.1에 기재되어 있습니다).특정 오브젝트 표현은 오브젝트유형의 값을 나타낼 필요는 없습니다.객체의 저장된 값이 이러한 표현을 가지며 문자 유형이 없는 lvalue 식에 의해 읽혀질 경우 동작은 정의되지 않습니다.이러한 표현이 문자 유형이 없는 lvalue 식에 의해 객체의 전부 또는 일부를 수정하는 부작용에 의해 생성되는 경우 동작은 정의되지 않습니다.50). 이러한 표현을 트랩 표현이라고 합니다.
(이 설명은 다음과 같은 플랫폼에만 적용됩니다.unsigned int
는 실제 시스템에서는 보기 드문 트랩 표현을 가질 수 있습니다.상세한 내용에 대해서는 코멘트를 참조해 주세요.또, 표준의 현행 어구로 이어지는 대체적이고 일반적인 원인에 대한 회부도 참조해 주세요.
네, 정의되지 않았습니다.코드가 크래쉬 할 수 있습니다.C는 일반적인 규칙에 예외를 둘 특별한 이유가 없기 때문에 동작이 정의되지 않았다고 말합니다.장점은 정의되지 않은 동작의 다른 모든 경우와 같은 장점입니다.컴파일러는 이 기능을 수행하기 위해 특별한 코드를 출력할 필요가 없습니다.
분명히 컴파일러는 변수 내에서 "handy"라고 생각되는 가비지 값을 사용할 수 있으며, 의도한 대로 작동합니다.그 접근법이 뭐가 문제죠?
왜 그런 일이 일어나지 않는다고 생각하세요?그게 바로 그 방법이야.컴파일러가 동작하도록 할 필요는 없지만, 실패하도록 할 필요는 없습니다.
많은 답변이 초기화되지 않은 레지스터 액세스를 트랩하는 프로세서에 초점을 맞추고 있지만 UB를 이용하기 위해 특별히 노력하지 않는 컴파일러를 사용하여 이러한 트랩이 없는 플랫폼에서도 이상한 동작이 발생할 수 있습니다.코드를 고려합니다.
volatile uint32_t a,b;
uin16_t moo(uint32_t x, uint16_t y, uint32_t z)
{
uint16_t temp;
if (a)
temp = y;
else if (b)
temp = z;
return temp;
}
로드 및 저장 이외의 모든 명령이 32비트 레지스터에서 작동하는 ARM과 같은 플랫폼용 컴파일러는 다음과 같은 방식으로 코드를 합리적으로 처리할 수 있습니다.
volatile uint32_t a,b;
// Note: y is known to be 0..65535
// x, y, and z are received in 32-bit registers r0, r1, r2
uin32_t moo(uint32_t x, uint32_t y, uint32_t z)
{
// Since x is never used past this point, and since the return value
// will need to be in r0, a compiler could map temp to r0
uint32_t temp;
if (a)
temp = y;
else if (b)
temp = z & 0xFFFF;
return temp;
}
어느 하나의 휘발성 읽기 값이 0이 아닌 경우 r0은 0 ~65535 범위의 값으로 로드됩니다.그렇지 않으면 함수가 호출되었을 때 보유하고 있던 값(즉, x로 전달된 값)이 모두 생성되며, 0~65535 범위의 값이 아닐 수 있습니다.이 기준서는 유형별로 uint16_t이지만 값이 0~65535 범위를 벗어나는 값의 행동을 기술할 수 있는 용어가 없다.
(이 답변은 C 1999를 대상으로 하고 있습니다.C 2011 의 경우는, Jens Gustedt 의 회답을 참조해 주세요).
C 표준은 초기화되지 않은 자동 저장 기간의 개체 값을 사용하는 것이 정의되지 않은 동작이라고 하지 않습니다.C 1999 표준은 6.7.8 10에서 "자동 저장 기간을 가진 객체가 명시적으로 초기화되지 않은 경우 그 값은 무한하다"고 규정하고 있습니다(이 항에서는 정적 객체의 초기화 방법을 정의하기 위해 계속되므로 초기화되지 않은 객체는 자동 객체뿐입니다).
3.17.2에서는 "불특정값 또는 트랩 표현"으로 정의하며, 3.17.3에서는 "불특정값"을 "이 국제표준이 어떤 경우에 어떤 값이 선택되는지에 대한 요건을 부과하지 않는 관련 유형의 유효한 값"으로 정의한다.
즉, 미초기화가unsigned int x
값이 지정되지 않은 경우x -= x
0을 생성해야 합니다.그러면 그것이 함정 표현일 수 있는지에 대한 의문이 남는다.트랩 값에 액세스하면 6.2.6.1 5에 따라 정의되지 않은 동작이 발생합니다.
일부 유형의 개체에는 부동소수점 번호의 시그널링 NaN 등 트랩 표현이 있을 수 있습니다.하지만 부호 없는 정수는 특별합니다.6.2.6.2에 따르면 부호 없는 int의 각 N개 값 비트는 2의 거듭제곱을 나타내고 값 비트의 각 조합은 0 ~2-1의N 값 중 하나를 나타냅니다.따라서 부호 없는 정수는 패딩 비트 내의 일부 값(패리티 비트 등)에 의해서만 트랩 표현을 가질 수 있습니다.
타깃 플랫폼에서 서명되지 않은 int에 패딩 비트가 없는 경우 초기화되지 않은 서명되지 않은 int에는 트랩 표현을 사용할 수 없으며 값을 사용하여 정의되지 않은 동작을 일으킬 수 없습니다.
언급URL : https://stackoverflow.com/questions/11962457/why-is-using-an-uninitialized-variable-undefined-behavior
'programing' 카테고리의 다른 글
vuetify: 동적 목록을 사용하는 v-chip v-model (0) | 2022.08.18 |
---|---|
C에서 "미사용 파라미터" 경고를 억제하려면 어떻게 해야 합니까? (0) | 2022.08.18 |
C++ 또는 C에서 foo(void)와 foo() 사이에 차이가 있습니까? (0) | 2022.08.18 |
double-c-language에서 절대값을 얻는 방법 (0) | 2022.08.18 |
vue2에서의 서비스JS - 생성된 후크 오류 (0) | 2022.08.18 |