programing

(왜) 초기화되지 않은 변수 정의되지 않은 동작을 사용하고 있습니까?

nasanasas 2020. 10. 14. 07:53
반응형

(왜) 초기화되지 않은 변수 정의되지 않은 동작을 사용하고 있습니까?


만약 내가 가지고 있다면:

unsigned int x;
x -= x;

이 표현식 뒤에는 0이 x 되어야한다는 것이 분명 하지만, 제가 보는 모든 곳에서 그들은 이 코드 동작x(빼기 전까지) 값이 아니라 정의되지 않았다고 말합니다 .

두 가지 질문 :

  • 이 코드 동작 이 실제로 정의되지 않았습니까?
    (예 : 호환 시스템에서 코드가 충돌하거나 더 나빠질 수 있습니까?)

  • 그렇다면 C 여기서 0 되어야한다는 것이 완벽하게 분명 할 때 동작 이 정의되지 않았다고 말하는 이유무엇입니까?x

    여기에서 행동을 정의하지 않으면 어떤 이점이 있습니까?

분명히 컴파일러 는 변수 내에서 "편리한"것으로 간주되는 모든 가비지 값을 사용할 수 있으며 의도 한대로 작동합니다. 그 접근 방식에 어떤 문제가 있습니까?


예,이 동작은 정의되지 않았지만 대부분의 사람들이 알고있는 것과는 다른 이유 때문입니다.

첫째, 단일화 된 값을 사용하는 것은 그 자체로 정의되지 않은 동작이 아니지만 값은 단순히 불확실합니다. 값이 유형에 대한 트랩 표현 인 경우 여기에 액세스하는 것은 UB입니다. 서명되지 않은 유형에는 트랩 표현이 거의 없으므로 그 쪽에서 상대적으로 안전합니다.

비헤이비어를 정의하지 않게 만드는 것은 변수의 추가 속성입니다. 즉, register주소를 사용하지 않는 " 선언 할 수있는"변수 입니다. 이러한 변수는 "초기화되지 않은"일종의 추가 상태가 있고 유형 도메인의 값에 해당하지 않는 실제 CPU 레지스터가있는 아키텍처가 있기 때문에 특별히 처리됩니다.

편집 : 표준의 관련 문구는 6.3.2.1p2입니다.

lvalue가 레지스터 스토리지 클래스로 선언 할 수있는 자동 저장 기간의 객체를 지정하고 (그 주소를 사용하지 않은 경우) 해당 객체가 초기화되지 않은 경우 (이니셜 라이저로 선언되지 않고 사용 전에 할당이 수행되지 않은 경우) ), 동작이 정의되지 않았습니다.

명확하게하기 위해 다음 코드 모든 상황에서 합법적입니다.

unsigned char a, b;
memcpy(&a, &b, 1);
a -= a;
  • 여기서 a의 주소를 b가져 오므로 값은 불확실합니다.
  • unsigned char불확실한 값이 지정되지 않은 트랩 표현이 없기 때문에의 모든 값 unsigned char이 발생할 수 있습니다.
  • 끝에는 값이 a 있어야합니다0 .

EDIT2 : ab지정되지 않은 값이 :

3.19.3 이 국제 표준이 어떤 경우에도 값이 선택되는 요건을 부과하지 않는 관련 유형의 불특정 값
유효 값


C 표준은 컴파일러에게 최적화를 수행 할 수있는 많은 권한을 제공합니다. 초기화되지 않은 메모리가 임의의 비트 패턴으로 설정되고 모든 작업이 작성된 순서대로 수행되는 프로그램의 순진한 모델을 가정하면 이러한 최적화의 결과는 놀라 울 수 있습니다.

참고 : 다음 예제는 x주소를 사용하지 않았기 때문에 유효하므로 "등록과 유사"합니다. 유형에 x트랩 표현이있는 경우에도 유효합니다 . 서명되지 않은 유형의 경우는 드물며 (최소한 1 비트의 스토리지를 "낭비"해야하며 문서화해야 함) unsigned char. x서명 된 유형이있는 경우 구현은-(2 n-1 -1)과 2 n-1 -1 사이의 숫자가 아닌 비트 패턴을 트랩 표현으로 정의 할 수 있습니다. Jens Gustedt의 답변을 참조하십시오 .

레지스터가 메모리보다 빠르기 때문에 컴파일러는 레지스터를 변수에 할당하려고합니다. 프로그램은 프로세서에있는 레지스터보다 더 많은 변수를 사용할 수 있기 때문에 컴파일러는 레지스터 할당을 수행하여 다른 시간에 동일한 레지스터를 사용하는 다른 변수를 만듭니다. 프로그램 조각을 고려하십시오

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은 컴파일러가 알아낼만큼 똑똑하지 않은 다른 조건으로 인해 발생할 수없는 일종의 우연이어야합니다. z4 번 줄 이후 에는 사용 x되지 않고 5 번 줄 이전에는 사용되지 않으므로 두 변수에 동일한 레지스터를 사용할 수 있습니다. 따라서이 작은 프로그램은 레지스터에 대한 다음 작업으로 컴파일됩니다.

r1 = 0;
r0 = 4;
r0 = - r0;
r1 += r0;
r0 = r1;

의 최종 값 x은의 최종 값 r0이고의 최종 값 y은의 최종 값입니다 r1. 이러한 값은 x = -3 및 y = -4이며 x제대로 초기화 된 경우 발생하는 5와 4가 아닙니다 .

보다 정교한 예를 보려면 다음 코드 조각을 고려하십시오.

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 : valuecondition; value

질문의 코드와 마주 쳤을 때이 동일한 컴파일러 x = - x는 평가 될 때 의 값 -x이 무엇이든 편리 하다는 것을 분석 합니다. 따라서 할당을 최적화 할 수 있습니다.

위에서 설명한대로 동작하는 컴파일러의 예를 찾지는 않았지만 좋은 컴파일러가 시도하는 일종의 최적화입니다. 나는 하나를 만나도 놀라지 않을 것입니다. 다음은 프로그램이 충돌하는 컴파일러의 덜 그럴듯한 예입니다. (어떤 종류의 고급 디버깅 모드에서 프로그램을 컴파일하는 것은 그다지 믿을 수없는 일이 아닙니다.)

이 가상 컴파일러는 다른 메모리 페이지의 모든 변수를 매핑하고 페이지 속성을 설정하여 초기화되지 않은 변수에서 읽는 것이 디버거를 호출하는 프로세서 트랩을 유발하도록합니다. 변수에 대한 모든 할당은 먼저 해당 메모리 페이지가 정상적으로 매핑되었는지 확인합니다. 이 컴파일러는 고급 최적화를 수행하지 않습니다. 초기화되지 않은 변수와 같은 버그를 쉽게 찾을 수 있도록 디버깅 모드에 있습니다. x = - x평가되고, 우측 트랩 발생 디버거 위로 발사.


예, 프로그램이 충돌 할 수 있습니다. 예를 들어, CPU 인터럽트를 유발할 수있는 트랩 표현 (처리 할 수없는 특정 비트 패턴)이있을 수 있으며, 처리되지 않으면 프로그램이 중단 될 수 있습니다.

(최근 C11 초안의 6.2.6.1에 따르면) 특정 개체 표현은 개체 유형의 값을 나타낼 필요가 없습니다. 객체의 저장된 값에 이러한 표현이 있고 문자 유형이없는 lvalue 표현식이 읽는 경우 동작이 정의되지 않습니다. 이러한 표현이 문자 유형이없는 lvalue 표현에 의해 객체의 전체 또는 일부를 수정하는 부작용에 의해 생성되는 경우 동작은 정의되지 않습니다 .50) 이러한 표현을 트랩 표현이라고합니다.

(이 설명 unsigned int은 실제 시스템에서는 드문 트랩 표현이있을 수있는 플랫폼에만 적용됩니다 . 표준의 현재 표현으로 이어지는 대체 및 아마도 더 일반적인 원인에 대한 자세한 내용과 참조는 주석을 참조하십시오.)


(이 답변은 C 1999를 다룹니다. C 2011의 경우 Jens Gustedt의 답변을 참조하십시오.)

C 표준은 초기화되지 않은 자동 저장 기간의 객체 값을 사용하는 것이 정의되지 않은 동작이라고 말하지 않습니다. C 1999 표준은 6.7.8 10에서 "자동 저장 기간이있는 객체가 명시 적으로 초기화되지 않으면 그 값은 불확실합니다"라고 말합니다. (이 단락은 정적 객체가 초기화되는 방법을 정의하므로 우리가 염려하는 유일한 초기화되지 않은 객체는 자동 객체입니다.)

3.17.2는 "미정 값"을 "미지정 값 또는 트랩 표현"으로 정의합니다. 3.17.3은 "미지정 값"을 "이 국제 표준이 어떤 경우에도 값이 선택되는 요건을 부과하지 않는 관련 유형의 유효한 값"으로 정의합니다.

So, if the uninitialized unsigned int x has an unspecified value, then x -= x must produce zero. That leaves the question of whether it may be a trap representation. Accessing a trap value does cause undefined behavior, per 6.2.6.1 5.

Some types of objects may have trap representations, such as the signaling NaNs of floating-point numbers. But unsigned integers are special. Per 6.2.6.2, each of the N value bits of an unsigned int represents a power of 2, and each combination of the value bits represents one of the values from 0 to 2N-1. So unsigned integers can have trap representations only due to some values in their padding bits (such as a parity bit).

If, on your target platform, an unsigned int has no padding bits, then an uninitialized unsigned int cannot have a trap representation, and using its value cannot cause undefined behavior.


Yes, it's undefined. The code can crash. C says the behavior is undefined because there's no specific reason to make an exception to the general rule. The advantage is the same advantage as all other cases of undefined behavior -- the compiler doesn't have to output special code to make this work.

Clearly, the compiler could simply use whatever garbage value it deemed "handy" inside the variable, and it would work as intended... what's wrong with that approach?

Why do you think that doesn't happen? That's exactly the approach taken. The compiler isn't required to make it work, but it is not required to make it fail.


For any variable of any type, which is not initialized or for other reasons holds an indeterminate value, the following applies for code reading that value:

  • In case the variable has automatic storage duration and does not have its address taken, the code always invokes undefined behavior [1].
  • Otherwise, in case the system supports trap representations for the given variable type, the code always invokes undefined behavior [2].
  • Otherwise if there are no trap representations, the variable takes an unspecified value. There is no guarantee that this unspecified value is consistent each time the variable is read. However, it is guaranteed not to be a trap representation and it is therefore guaranteed not to invoke undefined behavior [3].

    The value can then be safely used without causing a program crash, although such code is not portable to systems with trap representations.


[1]: C11 6.3.2.1:

If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

[2]: C11 6.2.6.1:

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.50) Such a representation is called a trap representation.

[3] C11:

3.19.2
indeterminate value
either an unspecified value or a trap representation

3.19.3
unspecified value
valid value of the relevant type where this International Standard imposes no requirements on which value is chosen in any instance
NOTE An unspecified value cannot be a trap representation.

3.19.4
trap representation
an object representation that need not represent a value of the object type


While many answers focus on processors that trap on uninitialized-register access, quirky behaviors can arise even on platforms which have no such traps, using compilers that make no particular effort to exploit UB. Consider the code:

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;  
}

a compiler for a platform like the ARM where all instructions other than loads and stores operate on 32-bit registers might reasonably process the code in a fashion equivalent to:

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;  
}

If either volatile reads yield a non-zero value, r0 will get loaded with a value in the range 0...65535. Otherwise it will yield whatever it held when the function was called (i.e. the value passed into x), which might not be a value in the range 0..65535. The Standard lacks any terminology to describe the behavior of value whose type is uint16_t but whose value is outside the range of 0..65535, except to say that any action which could produce such behavior invokes UB.

참고URL : https://stackoverflow.com/questions/11962457/why-is-using-an-uninitialized-variable-undefined-behavior

반응형