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

StackOverflow https://stackoverflow.com/questions/367633

문제

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

말하기 :

a[i] = i++;

도움이 되었습니까?

해결책

바늘

  • 탈환 a NULL 바늘
  • 크기 0의 "새로운"할당으로 반환 된 포인터를 소환합니다.
  • 수명이 끝나는 개체에 대한 포인터 사용 (예 : 할당 된 객체 또는 삭제 된 객체를 쌓는 것)
  • 아직 초기화되지 않은 포인터를 소환합니다.
  • 배열의 경계 (위 또는 아래) 외부에서 결과를 산출하는 포인터 산술을 수행합니다.
  • 배열 끝을 넘어서 위치에서 포인터를 탈환합니다.
  • 포인터를 호환되지 않는 유형의 물체로 변환합니다
  • 사용 memcpy 겹치는 버퍼를 복사합니다.

버퍼 오버플로

  • 오프셋에서 객체 또는 배열을 읽거나 쓸 수있는 오프셋 또는 해당 물체의 크기를 넘어서 (스택/힙 오버플로)

정수 오버플로

  • 서명 된 정수 오버플로
  • 수학적으로 정의되지 않은 표현을 평가합니다
  • 음수에 의한 좌회전 값 (음의 양으로 오른쪽 이동은 구현 된 구현)
  • 값을 숫자의 비트 수보다 큰 금액으로 이동 int64_t i = 1; i <<= 72 정의되지 않았다)

유형, 캐스트 및 Const

  • 숫자 값을 대상 유형으로 표현할 수없는 값으로 캐스트 (직접 또는 static_cast를 통해)
  • 확실히 할당되기 전에 자동 변수를 사용합니다 (예 : int i; i++; cout << i;)
  • 다른 유형의 객체의 값 사용 volatile 또는 sig_atomic_t 신호를받을 때
  • 수명 동안 문자열 문자 또는 다른 const 객체를 수정하려고
  • 전처리 중에 넓은 문자열 문자로 좁은 것을 연결

기능 및 템플릿

  • 가치 회복 함수에서 값을 반환하지 않음 (직접 또는 직접 또는 트리 블록에서 흘러 나오면)
  • 동일한 엔티티에 대한 여러 가지 정의 (클래스, 템플릿, 열거, 인라인 함수, 정적 멤버 함수 등)
  • 템플릿의 인스턴스화에서 무한 재귀
  • 다른 매개 변수를 사용하여 함수를 호출하거나 매개 변수에 대한 연결 및 함수가 사용하는 것으로 정의되는 연결.

OOP

  • 정적 저장 시간을 가진 물체의 계단식 파괴
  • 부분적으로 겹치는 객체에 할당 한 결과
  • 정적 객체의 초기화 중에 함수를 재귀 적으로 다시 입력
  • 생성자 또는 파괴자로부터 객체의 순수한 가상 함수로 가상 함수 호출
  • 구성되지 않았거나 이미 파괴 된 물체의 비 종자 멤버를 언급

소스 파일 및 전처리

  • Newline으로 끝나지 않거나 백 슬래시로 끝나지 않는 비어 있지 않은 소스 파일 (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.

내가 가장 좋아하는 것은 "템플릿의 인스턴스화에서 무한 재귀"입니다. 왜냐하면 그것이 컴파일 시간에 정의되지 않은 동작이 발생하는 유일한 사람이라고 생각하기 때문입니다.

스트리핑 후 상수에 할당 constNess 사용 const_cast<>:

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

게다가 정의되지 않은 행동, 똑같이 불쾌한 것도 있습니다 구현 정의 된 동작.

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

구현 정의 된 동작은 표준에 의해 정의되지 않았지만 구현이 문서화 해야하는 프로그램에 의한 조치입니다. 예를 들어 스택 오버플로 질문에서 "Multibyte 캐릭터 리터럴"이 있습니다. 이것을 컴파일하지 못하는 C 컴파일러가 있습니까?.

구현 정의 된 동작은 포팅을 시작할 때만 물린 것입니다 (그러나 새 버전의 컴파일러로 업그레이드하는 것도 포팅됩니다!)

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

int i =1;
i = ++i;

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

다양한 환경 제한에 대한 기본적인 이해. 전체 목록은 C 사양의 5.2.4.1 절에 있습니다. 여기 몇 가지가 있습니다.

  • 한 기능 정의의 127 매개 변수
  • 한 기능 호출의 127 인수
  • 하나의 매크로 정의의 127 매개 변수
  • 하나의 매크로 호출에 대한 127 인수
  • 논리적 소스 라인의 4095 자
  • 문자 문자열 문자 문자 또는 넓은 문자열 리터럴의 4095 자 (연결 후)
  • 개체의 65535 바이트 (호스팅 된 환경에서만)
  • #included 파일에 대한 15 가지 수준
  • 1023 스위치 문에 대한 케이스 레이블 (Adenested Switch 문 제외)

실제로 스위치 명령문에 대한 1023 케이스 레이블의 한계에 약간 놀랐습니다. 생성 된 코드/lex/파서에 대해 초과하는 것이 상당히 쉽다는 것을 예수 할 수 있습니다.

이러한 한계를 초과하면 정의되지 않은 동작 (충돌, 보안 결함 등)이 있습니다.

그렇습니다. 이것이 C 사양에서 나온 것임을 알고 있지만 C ++는 이러한 기본 지원을 공유합니다.

사용 memcpy 겹치는 메모리 영역 사이에 복사합니다. 예를 들어:

char a[256] = {};
memcpy(a, a, sizeof(a));

C ++ 03 표준에 의해 사용되는 C 표준에 따라 동작이 정의되지 않습니다.

7.21.2.1 memcpy 함수

개요

1/ #include void * memcpy (void * 제한 S1, const void * Zowns S2, size_t n);

설명

2/ memcpy 함수는 S2에 의해 지적 된 객체에서 n 문자를 S1에 의해 가리키는 물체로 복사합니다. 겹치는 객체 사이에서 복사가 발생하면 동작이 정의되지 않습니다. 반환 3 Memcpy 함수는 S1의 값을 반환합니다.

7.21.2.2 Memmove 기능

개요

1 #include void *memmove (void *s1, const void *s2, size_t n);

설명

2 Memmove 함수는 S2에 의해 지적 된 객체에서 N 문자를 S1에 의해 가리키는 물체로 복사합니다. 복사는 S2에 의해 지적 된 객체의 N 문자가 먼저 S1과 S2에 의해 가리키는 객체와 겹치지 않는 임시 N 문자로 복사 된 것처럼, 임시 배열의 N 문자가 복사됩니다. 물체는 S1을 가리 켰습니다. 보고

3 Memmove 함수는 S1의 값을 반환합니다.

C ++가 크기를 보장하는 유일한 유형은 char. 그리고 크기는 1입니다. 다른 모든 유형의 크기는 플랫폼에 따라 다릅니다.

다른 컴파일 장치의 네임 스페이스 레벨 객체는 초기화 순서가 정의되지 않기 때문에 초기화를 위해 서로 의존해서는 안됩니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top