C의 결합에 대한 질문 - 한 유형으로 저장하고 다른 유형으로 읽음 - 구현이 정의되어 있습니까?

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

문제

K&R에서 C의 Union에 대해 읽었는데, 내가 이해한 한 Union의 단일 변수는 여러 유형 중 하나를 보유할 수 있으며, 어떤 것이 한 유형으로 저장되고 다른 유형으로 추출되면 결과는 순전히 구현 정의입니다.

이제 다음 코드 조각을 확인하세요.

#include<stdio.h>

int main(void)
{
  union a
  {
     int i;
     char ch[2];
  };

  union a u;
  u.ch[0] = 3;
  u.ch[1] = 2;

  printf("%d %d %d\n", u.ch[0], u.ch[1], u.i);

  return 0;
}

산출:

3 2 515

여기서는 u.ch 하지만 둘 다에서 검색 u.ch 그리고 u.i.구현이 정의되어 있습니까?아니면 내가 정말 어리석은 짓을 하고 있는 걸까?

나는 그것이 대부분의 다른 사람들에게 매우 초보자처럼 보일 수 있다는 것을 알고 있지만 그 결과의 이유를 알 수 없습니다.

감사해요.

도움이 되었습니까?

해결책

이것은 정의되지 않은 행동입니다. u.i 그리고 u.ch 동일한 메모리 주소에 있습니다. 따라서 하나에 쓰고 다른 사람으로부터 읽은 결과는 컴파일러, 플랫폼, 아키텍처, 때로는 컴파일러의 최적화 수준에 따라 다릅니다. 따라서 출력 u.i 항상 그런 것은 아닙니다 515.

예시

예를 들어 gcc 내 기계에는 두 가지 다른 답변이 생성됩니다 -O0 그리고 -O2.

  1. 내 기계에는 32 비트 리틀 엔디안 아키텍처가 있기 때문입니다 -O0 나는 2와 3으로 초기화 된 2 개의 최소 유의 한 바이트로 끝나고, 두 개의 가장 중요한 바이트는 초기화되지 않습니다. 따라서 노조의 기억은 다음과 같습니다. {3, 2, garbage, garbage}

    따라서 나는 비슷한 출력을 얻습니다 3 2 -1216937469.

  2. 와 함께 -O2, 나는 출력을 얻는다 3 2 515 당신처럼, 그것은 노조 기억을 만드는 것입니다 {3, 2, 0, 0}. 무슨 일이 일어나는지입니다 gcc 전화를 최적화합니다 printf 실제 값이 있으므로 어셈블리 출력은 다음과 같습니다.

    #include <stdio.h>
    int main() {
        printf("%d %d %d\n", 3, 2, 515);
        return 0;
    }
    

    값 515는이 질문에 대한 다른 답변에서 설명 된 다른대로 얻을 수 있습니다. 본질적으로 그것은 그시기를 의미합니다 gcc 통화를 최적화하면서, 제로를 초기화되지 않은 노조의 임의 값으로 선택했습니다.

한 노조원에게 편지를 쓰고 다른 노조원에게 읽습니다 대개 의미가 없지만 때로는 의미가 있습니다 엄격한 별칭으로 편집 된 프로그램에 유용 할 수 있습니다..

다른 팁

이 질문에 대한 대답은 역사적 맥락에 따라 달라집니다. 언어의 사양은 시간이 지남에 따라 바뀌기 때문입니다.그리고 이 문제는 변화의 영향을 받는 문제가 됩니다.

K&R을 읽고 있다고 하더군요.해당 책의 최신판(현재)에는 C 언어의 첫 번째 표준화 버전인 C89/90이 설명되어 있습니다.해당 버전의 C 언어에서는 공용체의 한 구성원을 작성하고 다른 구성원을 읽는 것은 정의되지 않은 동작.아니다 구현 정의 (다른 얘기지만) 하지만 한정되지 않은 행동.이 경우 언어 표준의 관련 부분은 6.5/7입니다.

이제 C 진화의 어느 시점에서(Technical Corrigendum 3이 적용된 언어 사양의 C99 버전) 갑자기 유형 말장난에 공용체를 사용하는 것이 합법화되었습니다.연합의 한 구성원을 쓰고 다른 구성원을 읽는 것입니다.

그렇게 하려고 시도하면 여전히 정의되지 않은 동작이 발생할 수 있습니다.읽은 값이 읽은 유형에 대해 유효하지 않은 경우(소위 "트랩 표현") 동작은 여전히 ​​정의되지 않습니다.그렇지 않으면 읽은 값이 구현에 정의되어 있습니다.

당신의 특정한 예는 유형 말장난에 비교적 안전합니다. int 에게 char[2] 정렬.C 언어에서는 객체의 내용을 char 배열(역시 6.5/7)로 재해석하는 것이 항상 적법합니다.

그러나 그 반대는 사실이 아닙니다.데이터 쓰기 char[2] 조합원을 배열한 다음 이를 다음과 같이 읽습니다. int 잠재적으로 트랩 표현을 생성하여 다음으로 이어질 수 있습니다. 정의되지 않은 동작.문자 배열의 길이가 전체를 포괄할 만큼 충분한 경우에도 잠재적인 위험이 존재합니다. int.

그러나 귀하의 특별한 경우에 int 보다 큰 일이 발생 char[2], int 배열 끝 너머의 초기화되지 않은 영역을 다루게 되어 다시 정의되지 않은 동작이 발생하게 됩니다.

출력의 이유는 기계에서 정수가 저장되기 때문입니다. 리틀 엔디언 형식 : 가장 중요하지 않은 바이트는 먼저 저장됩니다. 따라서 바이트 시퀀스 [3,2,0,0]는 정수 3+2*256 = 515를 나타냅니다.

이 결과는 특정 구현 및 플랫폼에 따라 다릅니다.

이러한 코드의 출력은 플랫폼 및 C 컴파일러 구현에 따라 다릅니다. 당신의 출력은 당신이 Litte-Endian 시스템 (아마도 x86) 에서이 코드를 실행하고 있다고 생각하게합니다. 515를 i에 넣고 디버거에서 그것을 보려면 가장 낮은 선두 바이트가 3이고 메모리의 다음 바이트는 2가 될 것임을 알 수 있습니다.

빅 엔디 언 시스템 에서이 작업을 수행 한 경우, 아마도 770 (16 비트 INT) 또는 50462720 (32 비트 INT를 가정)을 받았을 것입니다.

구현 의존적이며 결과는 다른 플랫폼/컴파일러에 따라 다를 수 있지만 이것이 일어나는 일인 것 같습니다.

이진의 515

1000000011

패딩 0은 2 바이트를 만들기 위해 패딩 (16 비트 int를 가정) :

0000001000000011

두 바이트는 다음과 같습니다.

00000010 and 00000011

그게 2 그리고 3

누군가가 왜 그들이 역전되었는지 설명하기를 바랍니다. 내 추측은 숯이 반전되지 않지만 int는 작은 엔디 니아입니다.

노동 조합에 할당 된 메모리의 양은 가장 큰 멤버를 저장하는 데 필요한 메모리와 같습니다. 이 경우 int가 16 비트이고 Char가 8 비트라고 가정하면 동일한 공간이 필요하므로 Union이 두 바이트가 할당됩니다.

char 어레이에 3 개의 (00000011)와 2 (00000010)를 할당하면 연합 상태는 다음과 같습니다. 0000001100000010. 이 연합에서 INT를 읽으면 모든 것을 정수로 변환합니다. 가정합니다 리틀 엔디언 LSB가 가장 낮은 주소로 저장되는 표현, 노조의 int는 다음과 같습니다. 0000001000000011 이것은 515의 이진입니다.

참고 : int가 32 비트 인 경우에도 마찬가지입니다. Amnon의 대답

32 비트 시스템 인 경우 int는 4 바이트이지만 2 바이트 만 초기화합니다. 비 초기 데이터에 액세스하는 것은 정의되지 않은 동작입니다.

16 비트 INT가있는 시스템을 사용하고 있다고 가정하면 여전히 구현이 정의되어 있습니다. 시스템이 작은 엔디 어인 경우 U.CH [0]은 UI 및 U.CH의 가장 중요한 바이트와 일치합니다.1 가장 중요한 바이트가 될 것입니다. 큰 엔디 어 시스템에서는 다른 방법입니다. 또한 C 표준은 구현이 사용하도록 강요하지 않습니다. 둘의 보완 서명 된 정수 값을 나타 내기 위해서는 2의 보완이 가장 일반적이지만. 분명히 정수의 크기도 구현되어 있습니다.

힌트 : 16 진수 값을 사용하면 무슨 일이 일어나고 있는지 알기가 더 쉽습니다. 작은 엔디 언 시스템에서는 16 진수의 결과는 0x0203입니다.

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