C ++ 프로그래머가 알아야 할 일반적인 정의되지 않은 동작은 무엇입니까? [닫은] 다시 열

C ++ 프로그래머가 알아야 할 일반적인 정의되지 않은 동작은 무엇입니까?

다음과 같이 말합니다.

a[i] = i++;


답변

바늘

  • NULL포인터 역 참조
  • 크기가 0 인 “새”할당에 의해 반환 된 포인터 역 참조
  • 수명이 종료 된 객체에 대한 포인터 사용 (예 : 스택 할당 객체 또는 삭제 된 객체)
  • 아직 초기화되지 않은 포인터 역 참조
  • 배열의 경계 (위 또는 아래)를 벗어난 결과를 생성하는 포인터 산술을 수행합니다.
  • 배열 끝을 넘는 위치에서 포인터를 역 참조합니다.
  • 호환되지 않는 유형의 객체로 포인터 변환
  • memcpy겹치는 버퍼 복사에 사용 .

버퍼 오버 플로우

  • 음의 오프셋 또는 해당 객체의 크기를 초과하는 오프셋에서 객체 또는 배열에 대한 읽기 또는 쓰기 (스택 / 힙 오버플로)

정수 오버플로

  • 부호있는 정수 오버플로
  • 수학적으로 정의되지 않은 표현식 평가
  • 음수로 왼쪽 이동 값 (음수로 오른쪽 이동은 구현이 정의 됨)
  • 숫자의 비트 수보다 크거나 같은 양만큼 값 이동 (예 : int64_t i = 1; i <<= 72정의되지 않음)

유형, 캐스트 및 콘 스트

  • 대상 유형으로 표시 할 수없는 값으로 숫자 값 캐스트 (직접 또는 static_cast를 통해)
  • 자동 변수를 지정하기 전에 자동 변수 사용 (예 : int i; i++; cout << i; )
  • volatile또는 이외의 유형의 객체 값 사용sig_atomic_t신호를 수신 수신
  • 수명 동안 문자열 리터럴 또는 다른 const 객체를 수정하려고 시도
  • 전처리 과정에서 폭이 넓은 문자열 리터럴로 좁은 연결

기능 및 템플릿

  • 값 반환 함수에서 값을 반환하지 않음 (직접 또는 try-block에서 흘러 나옴)
  • 동일한 엔터티에 대한 여러 가지 다른 정의 (클래스, 템플릿, 열거, 인라인 함수, 정적 멤버 함수 등)
  • 템플릿 인스턴스화에서 무한 재귀
  • 다른 매개 변수를 사용하여 함수 호출 또는 함수가 사용하는 것으로 정의 된 매개 변수 및 링크에 대한 링크.

죄송합니다

  • 정적 저장 시간을 갖는 객체의 계단식 파괴
  • 부분적으로 겹치는 객체에 할당 한 결과
  • 정적 객체를 초기화하는 동안 재귀 적으로 함수를 다시 입력
  • 생성자 또는 소멸자에서 객체의 순수한 가상 함수에 대한 가상 함수 호출
  • 구성되지 않았거나 이미 파괴 된 객체의 비 정적 멤버 참조

소스 파일 및 전처리

  • 개행으로 끝나지 않거나 백 슬래시로 끝나는 비어 있지 않은 소스 파일 (C ++ 11 이전)
  • 문자 또는 문자열 상수에서 지정된 이스케이프 코드의 일부가 아닌 문자 뒤에 오는 백 슬래시 (C ++ 11에서 구현 정의 됨).
  • 구현 제한 초과 (중첩 된 블록 수, 프로그램의 함수 수, 사용 가능한 스택 공간 …)
  • 로 표현할 수없는 전 처리기 숫자 값 long int
  • 함수형 매크로 정의의 왼쪽에있는 전처리 지시문
  • #if표현식 에서 정의 된 토큰을 동적으로 생성

분류되기

  • 정적 저장 기간을 가진 프로그램을 파괴하는 동안 호출 종료

답변

함수 매개 변수가 평가되는 순서는 지정되지 않은 동작 입니다. (이것은 정의되지 않은 동작 과 달리 프로그램이 충돌하거나 폭발하거나 피자를 주문하지 않습니다. .)

유일한 요구 사항은 함수를 호출하기 전에 모든 매개 변수를 완전히 평가해야한다는 것입니다.


이:

// The simple obvious one.
callFunc(getA(),getB());

이것과 동등 할 수 있습니다 :

int a = getA();
int b = getB();
callFunc(a,b);

아니면 이거:

int b = getB();
int a = getA();
callFunc(a,b);

둘 중 하나 일 수 있습니다. 컴파일러에 달려 있습니다. 부작용에 따라 결과가 중요 할 수 있습니다.


답변

컴파일러는 표현식의 평가 부분을 자유롭게 재정렬 할 수 있습니다 (의미가 변경되지 않은 경우).

원래 질문에서 :

a[i] = i++;

// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)

// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:

int rhs  = i++;
int lhs& = a[i];
lhs = rhs;

// or
int lhs& = a[i];
int rhs  = i++;
lhs = rhs;

이중 점검 잠금. 그리고 하나의 쉬운 실수가 있습니다.

A* a = new A("plop");

// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'

// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.

// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        a = new A("Plop");  // (Point A).
    }
}
a->doStuff();

// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
//           Remember (c) has been done thus 'a' is not NULL.
//           But the memory has not been initialized.
//           Thread 2 now executes doStuff() on an uninitialized variable.

// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        A* tmp = new A("Plop");  // (Point A).
        a = tmp;
    }
}
a->doStuff();

// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.

답변

필자가 가장 좋아하는 것은 “템플릿 인스턴스화의 무한 재귀”입니다. 컴파일 타임에 정의되지 않은 동작이 발생하는 유일한 이유라고 생각하기 때문입니다.


답변

다음을 const사용하여 벗겨짐 후 상수에 할당 const_cast<>:

const int i = 10;
int *p =  const_cast<int*>( &i );
*p = 1234; //Undefined

답변

undefined behavior 이외에도 구현이 정의 되지 않은 동작있습니다. .

정의되지 않은 동작은 프로그램이 결과가 표준에 의해 지정되지 않은 작업을 수행 할 때 발생합니다.

구현-정의 된 행동은 결과가 표준에 의해 정의되지는 않지만 구현이 문서화되어야하는 프로그램에 의한 행동이다. 예를 들어, 스택 오버플로 질문의 “멀티 바이트 문자 리터럴”입니다. 컴파일에 실패한 C 컴파일러가 있습니까? .

구현 정의 동작은 이식을 시작할 때만 물지 만 (새로운 버전의 컴파일러로 업그레이드하는 것도 이식 중입니다!)


답변

변수는 표현식에서 한 번만 (기술적으로 시퀀스 포인트 사이에서 한 번) 업데이트 될 수 있습니다.

int i =1;
i = ++i;

// Undefined. Assignment to 'i' twice in the same expression.