Programing

C 컴파일러가 정렬 패딩을 제거하기 위해 구조 부재를 재배열할 수 없는 이유는?

c10106 2022. 5. 23. 20:40
반응형

C 컴파일러가 정렬 패딩을 제거하기 위해 구조 부재를 재배열할 수 없는 이유는?

중복 가능:
GCC가 구조를 최적화하지 않는 이유는?
C++가 구조를 더 촘촘하게 만들지 않는 이유는?

32비트 x86 기계에서 다음 예를 들어 보십시오.

선형 제약으로 인해 다음 구조

struct s1 {
    char a;
    int b;
    char c;
    char d;
    char e;
}

멤버가 다음과 같이 재주문된 경우 더 많은 메모리가 저장됨(12바이트 대 8바이트)을 나타낼 수 있다.

struct s2 {
    int b;
    char a;
    char c;
    char d;
    char e;
}

나는 C/C++ 컴파일러가 이것을 할 수 없다는 것을 알고 있다.내 질문은 왜 그 언어가 이런 방식으로 디자인되었는지이다.결국 우리는 엄청난 양의 기억력을 낭비하게 될지도 모른다.struct_ref->b차이점은 신경 쓰지 않을 겁니다

편집: 매우 유용한 답변에 감사한다.당신은 왜 언어의 설계 방식 때문에 재배치가 되지 않는지 잘 설명해 준다.그러나 그것은 다음과 같은 생각을 하게 한다.재배치가 언어의 일부였다면 이러한 논쟁들은 여전히 남아있을 것인가?어떤 특정한 재배열 규칙이 있었다고 가정합시다. 그 규칙으로부터 우리는 최소한 그 규칙을 요구했었습니다.

  1. 우리는 실제로 필요한 경우에만 구조물을 재정비해야 한다(구조물이 이미 "긴축"되어 있는 경우에는 아무것도 하지 말아야 한다.
  2. 규칙은 구조체의 정의만 볼 뿐 내부 구조체 내부는 볼 수 없다.이렇게 하면 구조체 유형이 다른 구조물의 내부인지 여부에 관계없이 동일한 레이아웃을 가질 수 있다.
  3. 주어진 구조체의 컴파일된 메모리 레이아웃은 그 정의에 따라 예측할 수 있다(즉, 규칙이 고정됨).

당신의 주장을 하나하나 어루만지며 나는 이렇게 생각한다.

  • 낮은 수준의 데이터 매핑, "최소한의 놀라움의 요소": 구조체를 직접 타이트한 스타일로 작성하기만 하면(예: @Perry의 답변에서) 아무것도 변하지 않았다(필수 1)이상한 이유로 내부 패딩을 원하는 경우 더미 변수를 사용하여 패딩을 수동으로 삽입하거나 키워드/디렉티브가 있을 수 있다.

  • 컴파일러 차이: 요구사항 3은 이러한 문제를 제거한다.사실 @David Heffernan의 논평으로 보아, 서로 다른 컴파일러들이 다르게 패딩을 하기 때문에 오늘날 우리는 이런 문제가 있는 것 같다?

  • 최적화:재주문 포인트는 모두 (메모리) 최적화다.나는 여기서 많은 잠재력을 본다.우리는 패딩을 모두 제거할 수는 없겠지만, 나는 어떻게 재주문하는 것이 어떤 식으로든 최적화를 제한할 수 있는지 모르겠다.

  • 유형 주조:내가 보기에 이것이 가장 큰 문제인 것 같다.그럼에도 불구하고, 이 문제를 해결할 방법이 있을 것이다.규칙은 언어로 정해져 있기 때문에 컴파일러는 구성원들이 어떻게 순서를 바꾸었는지 파악하고 그에 따라 반응할 수 있다.위에서 언급한 바와 같이, 완전한 통제를 원하는 경우에는 언제나 재주문을 방지할 수 있을 것이다.또한 요구사항 2는 형식 안전 코드가 절대 깨지지 않도록 보장한다.

내가 이런 규칙이 말이 될 수 있다고 생각하는 이유는 구조 구성원을 유형보다 내용별로 그룹화하는 것이 더 자연스럽기 때문이다.또한 컴파일러는 내면의 구조물이 많을 때 나보다 가장 좋은 순서를 선택하는 것이 더 쉽다.최적의 레이아웃은 형식적으로 표현할 수 없는 것일 수도 있다.반면에, 그것은 언어를 더 복잡하게 만드는 것처럼 보일 것이고, 이것은 물론 단점이다.

나는 언어를 바꾸는 것에 대해 말하는 것이 아니라는 점에 유의한다. 언어를 다르게 설계할 수 있다면 말이다.

나의 질문이 가상적인 것이라는 것을 알지만, 나는 그 토론이 기계와 언어 설계의 하위 단계에 더 깊은 통찰력을 제공한다고 생각한다.

나 여기 새로 온 사람이라서, 이걸 위해 새로운 질문을 해야 할지 모르겠어.이런 경우라면 나에게 말해 주시오.

C 컴파일러가 필드를 자동으로 다시 정렬할 수 없는 이유는 여러 가지가 있다.

  • C 컴파일러는 그 여부를 모른다.struct현재 컴파일 단위를 벗어난 개체의 메모리 구조(예: 외부 라이브러리, 디스크의 파일, 네트워크 데이터, CPU 페이지 테이블 등)를 나타낸다.이러한 경우 데이터의 이진 구조도 컴파일러가 접근할 수 없는 위치에 정의되므로, 데이터 순서를 다시 지정한다.struct필드는 다른 정의와 일치하지 않는 데이터 유형을 생성한다.예를 들어 ZIP 파일의 헤더에는 잘못 정렬된 32비트 필드가 여러 개 포함되어 있다.필드를 다시 정렬하면 C 코드가 헤더를 직접 읽거나 쓸 수 없게 된다(ZIP 구현이 데이터에 직접 액세스하고자 한다고 가정할 경우).

    struct __attribute__((__packed__)) LocalFileHeader {
        uint32_t signature;
        uint16_t minVersion, flag, method, modTime, modDate;
        uint32_t crc32, compressedSize, uncompressedSize;
        uint16_t nameLength, extraLength;
    };
    

    packed속성은 컴파일러가 자연적 정렬에 따라 필드를 정렬하는 것을 방지하며, 필드 순서의 문제와 관련이 없다.의 분야를 재주문하는 것이 가능할 것이다.LocalFileHeader구조물의 크기가 최소이고 모든 장이 자연 맞춤에 맞춰 정렬되도록 한다.그러나 컴파일러는 구조체가 실제로 ZIP 파일 사양에 의해 정의되는 것을 알지 못하기 때문에 필드 순서를 변경할 수 없다.

  • C는 안전하지 않은 언어다.C 컴파일러는 다음과 같이 컴파일러에서 볼 수 있는 유형과 다른 유형을 통해 데이터에 액세스할 수 있는지 여부를 알지 못한다.

    struct S {
        char a;
        int b;
        char c;
    };
    
    struct S_head {
        char a;
    };
    
    struct S_ext {
        char a;
        int b;
        char c;
        int d;
        char e;
    };
    
    struct S s;
    struct S_head *head = (struct S_head*)&s;
    fn1(head);
    
    struct S_ext ext;
    struct S *sp = (struct S*)&ext;
    fn2(sp);
    

    이것은 특히 헤더 바로 뒤에 위치한 데이터의 유형 ID를 포함하는 경우에 널리 사용되는 낮은 레벨 프로그래밍 패턴이다.

  • 만약struct활자가 다른 것에 내장되어 있다.struct활자, 내면에 인라인으로 정렬하는 것은 불가능하다.struct:

    struct S {
        char a;
        int b;
        char c, d, e;
    };
    
    struct T {
        char a;
        struct S s; // Cannot inline S into T, 's' has to be compact in memory
        char b;
    };
    

    이것은 또한 일부 필드가 다음에서 이동한다는 것을 의미한다.S다음과 같은 경우, 구조체가 일부 최적화를 사용하지 않도록 설정한다.

    // Cannot fully optimize S
    struct BC { int b; char c; };
    struct S {
        char a;
        struct BC bc;
        char d, e;
    };
    
  • 대부분의 C 컴파일러는 컴파일러를 최적화하고 있기 때문에 구조체 필드를 재정렬하려면 새로운 최적화가 필요할 것이다.그러한 최적화가 프로그래머들이 쓸 수 있는 것보다 더 잘 할 수 있을지 의문이다.손으로 데이터 구조를 설계하는 것은 레지스터 할당, 기능 인라이닝, 일정한 폴딩, 스위치 문장의 이진 검색 변환 등과 같은 다른 컴파일러 작업에 비해 훨씬 적은 시간 소모적이다.따라서 컴파일러가 데이터 구조를 최적화함으로써 얻을 수 있는 이점은 기존의 컴파일러 최적화보다 구체성이 떨어지는 것으로 보인다.

C는 휴대할 수 없는 하드웨어와 포맷 종속 코드를 높은 수준의 언어로 작성할 수 있도록 설계되고 의도되었다.프로그래머의 등 뒤에서 구조 콘텐츠를 재배열하는 것은 그 능력을 파괴할 것이다.

NetB의 실제 코드 준수SD의 ip.h:


/*
 * Structure of an internet header, naked of options.
 */
struct ip {
#if BYTE_ORDER == LITTLE_ENDIAN
    unsigned int ip_hl:4,       /* header length */
             ip_v:4;        /* version */
#endif
#if BYTE_ORDER == BIG_ENDIAN
    unsigned int ip_v:4,        /* version */
             ip_hl:4;       /* header length */
#endif
    u_int8_t  ip_tos;       /* type of service */
    u_int16_t ip_len;       /* total length */
    u_int16_t ip_id;        /* identification */
    u_int16_t ip_off;       /* fragment offset field */
    u_int8_t  ip_ttl;       /* time to live */
    u_int8_t  ip_p;         /* protocol */
    u_int16_t ip_sum;       /* checksum */
    struct    in_addr ip_src, ip_dst; /* source and dest address */
} __packed;

그 구조는 IP 데이터그램의 헤더와 레이아웃에서 동일하다.이더넷 컨트롤러에 의해 노골적으로 입력된 메모리의 블롭을 IP 데이터그램 헤더로 직접 해석하는 데 사용된다.만약 컴파일러가 저자의 밑에서 임의로 내용을 다시 짜낸다면, 그것은 재앙이 될 것이라고 상상해 보십시오.

그리고 그렇다, 그것은 정확하게 휴대할 수 없다 (그리고 심지어 휴대할 수 없는 gcc 지침도 거기에 있다.__packed매크로) 하지만 그게 중요한 게 아니다.C는 운전 하드웨어에 대해 비휴대용 하이 레벨 코드를 쓸 수 있도록 특별히 설계되었다.그것이 삶에서 그것의 기능이다.

C[및 C++]는 시스템 프로그래밍 언어로 간주되어 포인터 등을 통해 하드웨어에 낮은 수준의 액세스를 제공한다.프로그래머는 데이터 청크에 접근하여 구조물에 캐스트할 수 있으며 [쉽게] 다양한 구성원에 접근할 수 있다.

또 다른 예는 가변 크기 데이터를 저장하는 아래의 구조물이다.

struct {
  uint32_t data_size;
  uint8_t  data[1]; // this has to be the last member
} _vv_a;

구조체와 같은 변수 선언은 변수에 대한 "공용" 표현으로 설계된다는 점을 명심하십시오.이것은 컴파일러에 의해 사용될 뿐만 아니라, 다른 컴파일러가 해당 데이터 유형을 나타내기 위해 사용할 수 있다.아마 .h 파일로 끝날 것이다.따라서 컴파일러가 구조체 내의 구성원들이 조직되는 방식을 자유자재로 이용하려면, 모든 컴파일러는 동일한 규칙을 따를 수 있어야 한다.그렇지 않으면, 앞서 언급했듯이 포인터 산술은 다른 컴파일러들 사이에서 혼동될 것이다.

는 매우 이다. 은 당의 첫 로 하기 때문이다. 왜냐하면 그것은 첫 번째 요소를 필요로 하기 때문이다.struct재입고되다이것은 가능한 것이 아니다. 왜냐하면 먼저 정의되는 요소는struct항상 오프셋에 있어야 함0이 허용된다면 ( 만약 이것이 허용된다면 많은 (보거스) 코드가 깨질 것이다.

보다 일반적으로 동일한 큰 물체 내부에 거주하는 하위 물체의 포인터는 항상 포인터 비교를 허용해야 한다.만약 당신이 순서를 거꾸로 한다면, 나는 이 기능을 사용하는 몇몇 코드가 깨질 것이라고 상상할 수 있다.그리고 그러한 비교를 위해, 정의 지점에서 컴파일러의 지식은 도움이 되지 않을 것이다: 하위 객체에 대한 포인터에는 더 큰 객체가 속하는 "표시"가 없다.이와 같이 다른 기능으로 전달되면 가능한 문맥의 모든 정보가 손실된다.

머리말 a.h를 가지고 있다고 가정하자.

struct s1 {
    char a;
    int b;
    char c;
    char d;
    char e;
}

이 라이브러리는 별도의 라이브러리에 속하며(이 중 알 수 없는 컴파일러에 의해 컴파일된 이진만 있음) 이 구조를 사용하여 이 라이브러리와 통신하고,

컴파일러가 원하는 방식으로 멤버를 재주문할 수 있도록 허용되면 클라이언트 컴파일러가 구조체를 있는 그대로 사용할지 아니면 최적화된 상태로 사용할지를 모르기 때문에 이것이 불가능할 것이다.b앞 또는 뒤쪽으로 이동) 또는 심지어 모든 구성원이 4바이트 간격으로 정렬된 상태로 완전히 패딩됨

이를 해결하기 위해 당신은 압축을 위한 결정론적 알고리즘을 정의할 수 있지만, 그것은 모든 컴파일러가 그것을 구현해야 하고 알고리즘이 (효율 면에서) 좋은 알고리즘이라는 것을 요구한다.패딩 규칙에 동의하는 것이 재주문하는 것보다 쉽다.

a를 추가하는 것은 쉽다.#pragma특정 구조물에 대한 레이아웃이 필요할 때 최적화하는 것이 문제가 되지 않도록 하기 위해

구조체는 물리적 하드웨어를 가장 낮은 수준에서 나타내기 위해 사용된다.그런 만큼 컴파일러는 그 정도 수준으로는 사물을 한 바퀴도 움직일 수 없다.

그러나 컴파일러가 프로그램 내부에서만 사용되는 순수 메모리 기반 구조물을 재조정할 수 있도록 하는 #pragma를 갖는 것은 불합리하지 않을 것이다.그러나 나는 그런 짐승에 대해서는 모른다(그러나 그것은 스쿼트를 의미하는 것이 아니다-나는 C/C++와 연락이 끊겼다)

WG14의 멤버가 아닌 나는 결정적인 말을 할 수는 없지만 나만의 생각이 있다.

  1. 그것은 최소한의 놀라움이라는 원칙에 위배될 것이다 - 내가 그것이 가장 공간 효율적이든 아니든 간에 내 요소들을 특정한 순서로 배열하고 싶은 말도 안 되는 타당한 이유가 있을 수 있고, 나는 컴파일러가 그 요소들을 재배열하는 것을 원하지 않을 것이다.

  2. 기존 코드의 비독점적인 양을 깰 수 있는 잠재력이 있다 - 구조체의 주소와 같은 것에 의존하는 많은 레거시 코드들이 있다(그런 가정을 한 많은 고전적인 MacOS 코드를 보았다).

C99 논거는 두 번째 사항("기존 코드가 중요하며, 기존 구현은 중요하지 않다")을 직접적으로 다루고, 첫 번째 사항("프로그래머를 신뢰하라")을 간접적으로 다룬다.

그것은 구조 부재의 순서를 변경하기 위해 포인터 작동의 의미 체계를 바꿀 것이다.컴팩트한 메모리 표현에 신경을 쓴다면, 당신의 목표 아키텍처를 알고 그에 따라 당신의 구조를 구성하는 것은 프로그래머로서의 당신의 책임이다.

C C struct회원들은 재앙이 될 것이다.예를 들어, 버퍼에서 구조물을 실제로 채울 수 있는 실질적인 방법은 없을 것이다.

내가 지금까지 보지 못한 이유가 여기에 있다. 표준 재배열 규칙이 없다면 소스 파일 간의 호환성을 깨뜨릴 것이다.

구조체가 헤더 파일에 정의되어 있고 두 개의 파일에 사용된다고 가정합시다.
두 파일 모두 별도로 컴파일되며 나중에 연결된다.컴파일은 서로 다른 시간(아마도 한 번만 건드렸기 때문에 컴파일을 다시 컴파일해야 했다), 다른 컴퓨터(네트워크 드라이브에 파일이 있는 경우) 또는 다른 컴파일러 버전일 수 있다.
만약 컴파일러가 한 번에 순서를 바꾸기로 결정한다면, 다른 한 때, 두 파일은 필드가 어디에 있는지 동의하지 않을 것이다.

예를 들면, 그 경우를 생각해 보자.stat시스템 호출 및struct stat.
예를 들어 Linux를 설치하면 libC(예:stat그것은 언젠가 누군가에 의해 편집된 것이다.
그런 다음 최적화 플래그를 사용하여 컴파일러와 응용프로그램을 컴파일러로 컴파일하고 두 프로그램이 구조물의 레이아웃에 대해 일치할 것으로 예상하십시오.

참조URL: https://stackoverflow.com/questions/9486364/why-cant-c-compilers-rearrange-struct-members-to-eliminate-alignment-padding

반응형