C에서 흔히 발생하는 정의되지 않은/지정되지 않은 동작은 무엇입니까?[닫은]

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

  •  01-07-2019
  •  | 
  •  

문제

C 언어에서 지정되지 않은 동작의 예로는 함수에 대한 인수 평가 순서가 있습니다.왼쪽에서 오른쪽일 수도 있고 오른쪽에서 왼쪽일 수도 있는데, 당신은 모릅니다.이는 방법에 영향을 미칩니다. foo(c++, c) 또는 foo(++c, c) 평가를 받습니다.

알지 못하는 프로그래머를 놀라게 할 수 있는 불특정 행동에는 또 어떤 것이 있습니까?

도움이 되었습니까?

해결책

언어변호사 질문입니다.흠케이.

내 개인적인 TOP3:

  1. 엄격한 앨리어싱 규칙 위반
  2. 엄격한 앨리어싱 규칙 위반
  3. 엄격한 앨리어싱 규칙 위반

    :-)

편집하다 다음은 두 번 잘못한 작은 예입니다.

(32비트 정수와 리틀 엔디안을 가정)

float funky_float_abs (float a)
{
  unsigned int temp = *(unsigned int *)&a;
  temp &= 0x7fffffff;
  return *(float *)&temp;
}

해당 코드는 부동 소수점 표현에서 부호 비트를 직접 비트 조작하여 부동 소수점의 절대값을 얻으려고 합니다.

그러나 한 유형에서 다른 유형으로 캐스팅하여 객체에 대한 포인터를 생성한 결과는 유효한 C가 아닙니다.컴파일러는 서로 다른 유형에 대한 포인터가 동일한 메모리 덩어리를 가리키지 않는다고 가정할 수 있습니다.이는 void* 및 char*를 제외한 모든 종류의 포인터에 해당됩니다(부호 여부는 중요하지 않음).

위의 경우에는 두 번 수행합니다.한 번은 float a에 대한 내부 별칭을 얻고, 한 번은 값을 다시 float로 변환합니다.

동일한 작업을 수행하는 세 가지 유효한 방법이 있습니다.

캐스트 중에 char 또는 void 포인터를 사용하십시오.이들은 항상 무엇이든 별칭을 지정하므로 안전합니다.

float funky_float_abs (float a)
{
  float temp_float = a;
  // valid, because it's a char pointer. These are special.
  unsigned char * temp = (unsigned char *)&temp_float;
  temp[3] &= 0x7f;
  return temp_float;
}

멤카피를 사용하세요.Memcpy는 void 포인터를 사용하므로 앨리어싱도 강제합니다.

float funky_float_abs (float a)
{
  int i;
  float result;
  memcpy (&i, &a, sizeof (int));
  i &= 0x7fffffff;
  memcpy (&result, &i, sizeof (int));
  return result;
}

세 번째 유효한 방법:노동조합을 이용하세요.이는 명시적으로 C99 이후로 정의되지 않았습니다.

float funky_float_abs (float a)
{
  union 
  {
     unsigned int i;
     float f;
  } cast_helper;

  cast_helper.f = a;
  cast_helper.i &= 0x7fffffff;
  return cast_helper.f;
}

다른 팁

개인적으로 가장 좋아하는 정의되지 않은 동작은 비어 있지 않은 소스 파일이 개행 문자로 끝나지 않으면 동작이 정의되지 않는다는 것입니다.

내가 볼 컴파일러는 경고를 내보내는 것 외에 개행 종료 여부에 따라 소스 파일을 다르게 처리하지 않았지만 이것이 사실이라고 생각합니다.따라서 경고에 놀라는 것 외에는 모르는 프로그래머를 놀라게 할 것은 아닙니다.

따라서 진정한 이식성 문제의 경우(대부분 지정되지 않거나 정의되지 않은 것이 아니라 구현에 따라 다르지만 이것이 질문의 정신에 해당한다고 생각합니다):

  • char은 반드시 서명되지는 않습니다.
  • int는 16비트부터 임의의 크기일 수 있습니다.
  • 부동 소수점은 반드시 IEEE 형식이거나 규격을 준수할 필요는 없습니다.
  • 정수 유형은 반드시 2의 보수일 필요는 없으며 정수 산술 오버플로로 인해 정의되지 않은 동작이 발생합니다(최신 하드웨어는 충돌하지 않지만 일부 컴파일러 최적화는 하드웨어가 하는 일임에도 불구하고 랩어라운드와 다른 동작을 초래합니다).예를 들어 if (x+1 < x) 다음과 같은 경우 항상 false로 최적화될 수 있습니다. x 다음 유형에 서명했습니다.보다 -fstrict-overflow GCC의 옵션).
  • "/", "." 그리고 ".."는 #include에서 정의 된 의미가 없으며 다른 컴파일러에 의해 다르게 취급 될 수 있습니다 (실제로는 다양하며 잘못되면 하루를 망칠 것입니다).

동작이 부분적으로만 정의되지 않거나 지정되지 않기 때문에 개발한 플랫폼에서도 놀랄 수 있는 정말 심각한 것입니다.

  • POSIX 스레딩 및 ANSI 메모리 모델.메모리에 대한 동시 액세스는 초보자가 생각하는 것만큼 잘 정의되어 있지 않습니다.휘발성은 초보자가 생각하는 것을 수행하지 않습니다.메모리 액세스 순서는 초보자가 생각하는 것만큼 잘 정의되어 있지 않습니다.액세스 ~할 수 있다 특정 방향으로 메모리 장벽을 넘어 이동할 수 있습니다.메모리 캐시 일관성은 필요하지 않습니다.

  • 코드 프로파일링은 생각만큼 쉽지 않습니다.테스트 루프가 효과가 없으면 컴파일러는 테스트 루프의 일부 또는 전부를 제거할 수 있습니다.인라인에는 정의된 효과가 없습니다.

그리고 내 생각에 Nils는 다음과 같이 언급했습니다.

  • 엄격한 앨리어싱 규칙을 위반합니다.

무언가에 대한 포인터로 무언가를 나누는 것입니다.어떤 이유로든 컴파일이 되지 않습니다...:-)

result = x/*y;

내가 가장 좋아하는 것은 이것입니다:

// what does this do?
x = x++;

일부 의견에 답변하자면 표준에 따라 정의되지 않은 동작입니다.이를 보면 컴파일러는 하드 드라이브 포맷을 포함한 모든 작업을 수행할 수 있습니다.예를 들어 참조 여기 이 댓글.요점은 어떤 행동에 대한 합리적인 기대가 가능하다는 것을 알 수 있다는 것이 아닙니다.C++ 표준과 시퀀스 포인트가 정의되는 방식으로 인해 이 코드 줄은 실제로 정의되지 않은 동작입니다.

예를 들어, x = 1 위 줄 이전에, 이후에 유효한 결과는 무엇입니까?누군가가 그렇게 해야 한다고 댓글을 달았죠.

x는 1씩 증가합니다.

그래서 우리는 나중에 x == 2를 봐야 합니다.그러나 이것은 실제로는 사실이 아닙니다. 나중에 x == 1이거나 심지어 x == 3인 일부 컴파일러를 찾을 수 있습니다.이것이 왜 발생하는지 확인하려면 생성된 어셈블리를 자세히 살펴봐야 하지만 차이점은 근본적인 문제로 인해 발생합니다.본질적으로 이것은 컴파일러가 원하는 순서로 두 개의 할당 문을 평가할 수 있기 때문이라고 생각합니다. x++ 먼저, 또는 x = 첫 번째.

내가 겪은 또 다른 문제(정의되어 있지만 확실히 예상치 못한 문제)입니다.

char는 사악합니다.

  • 컴파일러가 느끼는 바에 따라 서명되거나 서명되지 않음
  • ~ 아니다 8비트로 규정

나는 그들의 인수와 일치하도록 printf 형식 지정자를 수정한 횟수를 셀 수 없습니다. 불일치는 정의되지 않은 동작입니다..

  • 아니요, 통과하면 안 됩니다. int (또는 long) 에게 %x - unsigned int 필수
  • 아니요, 통과하면 안 됩니다. unsigned int 에게 %d - int 필수
  • 아니요, 통과하면 안 됩니다. size_t 에게 %u 또는 %d - 사용 %zu
  • 아니요, 다음과 같이 포인터를 인쇄하면 안 됩니다. %d 또는 %x - 사용 %p 그리고 void *

함수 프로토타입을 사용할 수 없는 경우 컴파일러는 잘못된 수의 매개변수/잘못된 매개변수 유형으로 함수를 호출하고 있음을 알려줄 필요가 없습니다.

나는 상대적으로 경험이 부족한 프로그래머들이 다중 문자 상수에 물린 것을 많이 보았습니다.

이것:

"x"

문자열 리터럴입니다(유형은 char[2] 그리고 붕괴 char* 대부분의 상황에서).

이것:

'x'

일반 문자 상수입니다(역사적인 이유로 다음과 같은 유형입니다). int).

이것:

'xy'

또한 완벽하게 유효한 문자 상수이지만 그 값(여전히 유형입니다. int)는 구현에 따라 정의됩니다.이는 대부분 혼란을 야기하는 거의 쓸모없는 언어 기능입니다.

clang 개발자가 일부를 게시했습니다. 좋은 예 얼마 전 모든 C 프로그래머가 읽어야 할 게시물이 있었습니다.이전에 언급되지 않은 몇 가지 흥미로운 사항은 다음과 같습니다.

  • 부호 있는 정수 오버플로 - 아니요, 최대 값을 초과하여 부호 있는 변수를 래핑하는 것은 좋지 않습니다.
  • NULL 포인터 역참조 - 예, 이는 정의되지 않았으며 무시될 수 있습니다. 링크의 2부를 참조하세요.

여기 EE는 방금 a>>-2가 약간 문제가 있다는 것을 발견했습니다.

나는 고개를 끄덕이며 그것은 자연스럽지 않다고 말했습니다.

변수를 사용하기 전에 항상 변수를 초기화해야 합니다!C를 막 시작했을 때, 그것은 나에게 많은 두통을 야기했습니다.

"max" 또는 "isupper"와 같은 매크로 버전의 함수를 사용합니다.매크로는 인수를 두 번 평가하므로 max(++i, j) 또는 isupper(*p++)를 호출할 때 예상치 못한 부작용이 발생합니다.

위의 내용은 표준 C에 대한 것입니다.C++에서는 이러한 문제가 대부분 사라졌습니다.max 함수는 이제 템플릿 함수입니다.

추가하는 걸 잊어버렸어 static float foo(); 헤더 파일에서는 0.0f를 반환할 때만 부동 소수점 예외가 발생합니다.

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