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
그 차이에 대해 신경 쓰지 않는 것과 같은 참조 가 있습니다.
편집 : 매우 유용한 답변에 감사드립니다. 언어가 설계된 방식 때문에 재 배열이 작동하지 않는 이유를 잘 설명합니다. 그러나 그것은 나를 생각하게합니다 : 재 배열이 언어의 일부라면 이러한 주장이 여전히 유효할까요? 특정 재배치 규칙이 있다고 가정 해 보겠습니다.
- 실제로 필요한 경우에만 구조체를 재구성해야합니다 (구조체가 이미 "tight"인 경우 아무 작업도하지 마십시오).
- 규칙은 내부 구조체 내부가 아니라 구조체의 정의 만 확인합니다. 이렇게하면 구조체 유형이 다른 구조체의 내부인지 여부에 관계없이 동일한 레이아웃을 갖게됩니다.
- 주어진 구조체의 컴파일 된 메모리 레이아웃은 정의에 따라 예측 가능합니다 (즉, 규칙이 고정됨).
나는 당신의 주장을 하나씩 설명한다.
저수준 데이터 매핑, "최소한의 놀라움 요소" : 구조를 타이트한 스타일로 직접 작성하면 (@Perry의 답변처럼) 아무것도 변경되지 않았습니다 (요구 사항 1). 이상한 이유로 내부 패딩을 원하면 더미 변수를 사용하여 수동으로 삽입 할 수 있으며 키워드 / 지시문이있을 수 있습니다.
컴파일러 차이점 : 요구 사항 3은 이러한 문제를 제거합니다. 실제로 @David Heffernan의 의견에 따르면 다른 컴파일러가 다르게 패딩하기 때문에 오늘날이 문제가있는 것 같습니다.
최적화 : 재정렬의 요점은 (메모리) 최적화입니다. 여기에 많은 잠재력이 있습니다. 패딩을 모두 제거 할 수는 없지만 재정렬이 어떻게 최적화를 제한 할 수 있는지는 알 수 없습니다.
타입 캐스팅 : 이것이 가장 큰 문제인 것 같습니다. 그래도이 문제를 해결할 방법이 있어야합니다. 규칙이 언어로 고정되어 있기 때문에 컴파일러는 멤버가 어떻게 재정렬되었는지 파악하고 그에 따라 반응 할 수 있습니다. 위에서 언급했듯이 완전한 제어를 원하는 경우 항상 재주문을 방지 할 수 있습니다. 또한 요구 사항 2는 형식 안전 코드가 깨지지 않도록합니다.
이러한 규칙이 합리적이라고 생각하는 이유는 struct 멤버를 유형보다 내용별로 그룹화하는 것이 더 자연 스럽기 때문입니다. 또한 내부 구조체가 많을 때 컴파일러가 나보다 최상의 순서를 선택하는 것이 더 쉽습니다. 최적의 레이아웃은 타입 안전 방식으로 표현할 수없는 레이아웃 일 수도 있습니다. 반면에 언어를 더 복잡하게 만드는 것처럼 보일 수 있지만 이는 당연히 단점입니다.
내가 언어 변경에 대해 이야기하는 것이 아니라 다르게 설계 할 수있는 경우에만 (/해야 할 경우)
나는 내 질문이 가설이라는 것을 알고 있지만 토론은 낮은 수준의 기계 및 언어 설계에 대한 더 깊은 통찰력을 제공한다고 생각합니다.
나는 여기에서 꽤 새롭기 때문에 이것에 대한 새로운 질문을 만들어야할지 모르겠습니다. 이 경우 알려주세요.
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; };
The
packed
attribute prevents the compiler from aligning the fields according to their natural alignment, and it has no relation to the problem of field ordering. It would be possible to reorder the fields ofLocalFileHeader
so that the structure has both minimal size and has all fields aligned to their natural alignment. However, the compiler cannot choose to reorder the fields because it does not know that the struct is actually defined by the ZIP file specification.C is an unsafe language. The C compiler doesn't know whether the data will be accessed via a different type than the one seen by the compiler, for example:
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);
This is a widely used low-level programming pattern, especially if the header contains the type ID of data located just beyond the header.
If a
struct
type is embedded in anotherstruct
type, it is impossible to inline the innerstruct
: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; };
This also means that moving some fields from
S
to a separate struct disables some optimizations:// Cannot fully optimize S struct BC { int b; char c; }; struct S { char a; struct BC bc; char d, e; };
Because most C compilers are optimizing compilers, reordering struct fields would require new optimizations to be implemented. It is questionable whether those optimizations would be able to do better than what programmers are able to write. Designing data structures by hand is much less time consuming than other compiler tasks such as register allocation, function inlining, constant folding, transformation of a switch statement into binary search, etc. Thus the benefits to be gained by allowing the compiler to optimize data structures appear to be less tangible than traditional compiler optimizations.
C is designed and intended to make it possible to write non-portable hardware and format dependent code in a high level language. Rearrangement of structure contents behind the back of the programmer would destroy that ability.
Observe this actual code from NetBSD's 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;
That structure is identical in layout to the header of an IP datagram. It is used to directly interpret blobs of memory blatted in by an ethernet controller as IP datagram headers. Imagine if the compiler arbitrarily re-arranged the contents out from under the author -- it would be a disaster.
And yes, it isn't precisely portable (and there's even a non-portable gcc directive given there via the __packed
macro) but that's not the point. C is specifically designed to make it possible to write non-portable high level code for driving hardware. That's its function in life.
C [and C++] are regarded as systems programming languages so they provide low level access to the hardware, e.g., memory by means of pointers. Programmer can access a data chunk and cast it to a structure and access various members [easily].
Another example is a struct like the one below, which stores variable sized data.
struct {
uint32_t data_size;
uint8_t data[1]; // this has to be the last member
} _vv_a;
Not being a member of WG14, I can't say anything definitive, but I have my own ideas:
It would violate the principle of least surprise - there may be a damned good reason why I want to lay my elements out in a specific order, regardless of whether or not it's the most space-efficient, and I would not want the compiler to rearrange those elements;
It has the potential to break a non-trivial amount of existing code - there's a lot of legacy code out there that relies on things like the address of the struct being the same as the address of the first member (saw a lot of classic MacOS code that made that assumption);
The C99 Rationale directly addresses the second point ("Existing code is important, existing implementations are not") and indirectly addresses the first ("Trust the programmer").
It would change the semantics of pointer operations to reorder the structure members. If you care about compact memory representation, it's your responsibility as a programmer to know your target architecture, and organize your structures accordingly.
If you were reading/writing binary data to/from C structures, reordering of the struct
members would be a disaster. There would be no practical way to actually populate the structure from a buffer, for example.
Structs are used to represent physical hardware at the very lowest levels. As such the compiler cannot move things a round to suit at that level.
However it would not be unreasonable to have a #pragma that let the compiler re-arrange purely memory based structs that are only used internally to the program. However I don't know of such a beast (but that doesn't meant squat - I'm out of touch with C/C++)
Keep in mind that a variable declaration, such as a struct, is designed to be a "public" representation of the variable. It's used not only by your compiler, but is also available to other compilers as representing that data type. It will probably end up in a .h file. Therefore, if a compiler is going to take liberties with the way the members within a struct are organized, then ALL compilers have to be able to follow the same rules. Otherwise, as has been mentioned, the pointer arithmetic will get confused between different compilers.
Here's a reason I didn't see so far - without standard rearrangement rules, it would break compatibility between source files.
Suppose a struct is defined in a header file, and used in two files.
Both files are compiled separately, and later linked. Compilation may be at different times (maybe you touched just one, so it had to be recompiled), possibly on different computers (if the files are on a network drive) or even different compiler versions.
If at one time, the compiler would decide to reorder, and at another it won't, the two files won't agree on where the fields are.
As an example, think of the stat
system call and struct stat
.
When you install Linux (for example), you get libC, which includes stat
, which was compiled by someone sometime.
You then compile an application with your compiler, with your optimization flags, and expect both to agree on the struct's layout.
Your case is very specific as it would require the first element of a struct
to be put re-ordered. This is not possible, since the element that is defined first in a struct
must always be at offset 0
. A lot of (bogus) code would break if this would be allowed.
More generally pointers of subobjects that live inside the same bigger object must always allow for pointer comparison. I can imagine that some code that uses this feature would break if you'd invert the order. And for that comparison the knowledge of the compiler at the point of definition wouldn't help: a pointer to a subobject doesn't have a "mark" do which larger object it belongs. When passed to another function just as such, all information of a possible context is lost.
suppose you have a header a.h with
struct s1 {
char a;
int b;
char c;
char d;
char e;
}
and this is part of a separate library (of which you only have the compiled binaries compiled by a unknown compiler) and you wish to use this struct to communicate with this library,
if the compiler is allowed to reorder the members in whichever way it pleases this will be impossible as the client compiler doesn't know whether to use the struct as-is or optimized (and then does b
go in front or in the back) or even fully padded with every member aligned on 4 byte intervals
to solve this you can define a deterministic algorithm for compacting but that requires all compilers to implement it and that the algorithm is a good one (in terms of efficiency). it is easier to just agree on padding rules than it is on reordering
it is easy to add a #pragma
that prohibits the optimization for when you need the layout of to a specific struct be exactly what you need so that is no issue
'development' 카테고리의 다른 글
Ruby on Rails-애플리케이션 레이아웃없이 액션을 렌더링하려면 어떻게해야합니까? (0) | 2020.09.09 |
---|---|
.bashrc / .profile이 새 tmux 세션 (또는 창)에로드되지 않는 이유는 무엇입니까? (0) | 2020.09.09 |
Fiddler가 설치하는 루트 CA 인증서를 제거하는 방법 (0) | 2020.09.09 |
XMLHttpRequest가 XXX No 'Access-Control-Allow-Origin'헤더를로드 할 수 없습니다. (0) | 2020.09.09 |
CSS 색상 변수에 불투명도를 어떻게 적용합니까? (0) | 2020.09.09 |