Linux 커널 코드에서 이해할 수없는 다음을 발견했습니다.
struct bts_action {
u16 type;
u16 size;
u8 data[0];
} __attribute__ ((packed));
코드는 다음과 같습니다. http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h
요소가없는 데이터 배열의 필요성과 목적은 무엇입니까?
답변
이것은 malloc
( kmalloc
이 경우) 두 번 호출하지 않고도 다양한 크기의 데이터를 가질 수있는 방법 입니다. 다음과 같이 사용합니다.
struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);
이것은 표준이 아니었고 해킹으로 간주되었지만 (Aniket이 말했듯이) C99 에서 표준화되었습니다 . 현재 표준 형식은 다음과 같습니다.
struct bts_action {
u16 type;
u16 size;
u8 data[];
} __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */
data
필드의 크기는 언급하지 않습니다 . 이 특수 변수는 구조체의 끝에 만 올 수 있습니다.
C99에서이 문제는 6.7.2.1.16 (강조 내)에 설명되어 있습니다.
특별한 경우로 이름이 지정된 멤버가 둘 이상있는 구조의 마지막 요소에 불완전한 배열 유형이있을 수 있습니다. 이를 유연한 배열 멤버라고합니다.. 대부분의 상황에서 유연한 배열 구성원은 무시됩니다. 특히 구조의 크기는 생략이 의미하는 것보다 더 많은 후행 패딩이있을 수 있다는 점을 제외하고는 유연한 배열 멤버가 생략 된 것과 같습니다. 그러나. (또는->) 연산자에는 유연한 배열 멤버가있는 구조 (에 대한 포인터) 인 왼쪽 피연산자가 있고 해당 멤버의 오른쪽 피연산자 이름을 지정하면 해당 멤버가 동일한 요소 유형을 가진 가장 긴 배열로 대체 된 것처럼 동작합니다. ) 액세스되는 객체보다 구조를 더 크게 만들지 않습니다. 배열의 오프셋은 대체 배열의 오프셋과 다르더라도 유연한 배열 구성원의 오프셋으로 유지됩니다. 이 배열에 요소가 없으면
즉, 다음이있는 경우 :
struct something
{
/* other variables */
char data[];
}
struct something *var = malloc(sizeof(*var) + extra);
var->data
에서 인덱스로 액세스 할 수 있습니다 [0, extra)
. 주 sizeof(struct something)
에만 다른 변수의 크기 회계를 줄 것이다는, 즉 제공 data
공의 크기.
표준이 실제로 malloc
그러한 구성 (6.7.2.1.17)의 예를 제공하는 방법에 주목하는 것도 흥미로울 수 있습니다 .
struct s { int n; double d[]; };
int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
동일한 위치에있는 표준의 또 다른 흥미로운 메모는 다음과 같습니다.
malloc 호출이 성공했다고 가정하면 p가 가리키는 객체는 대부분의 목적에서 p가 다음과 같이 선언 된 것처럼 동작합니다.
struct { int n; double d[m]; } *p;
(이 동등성이 깨지는 상황이 있습니다. 특히 멤버 d의 오프셋이 동일하지 않을 수 있습니다 .)
답변
구조체 해킹 이라고도합니다. .
그래서 다음에 저는 이렇게 말할 것입니다.
struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);
다음과 같이 말하는 것과 같습니다.
struct bts_action{
u16 type;
u16 size;
u8 data[100];
};
그리고 이러한 struct 객체를 얼마든지 만들 수 있습니다.
답변
아이디어는 구조체의 끝에 가변 크기 배열을 허용하는 것입니다. 아마도 bts_action
고정 크기 헤더 ( type
및 size
필드)와 가변 크기 data
멤버 가있는 일부 데이터 패킷 일 것입니다 . 길이가 0 인 배열로 선언하면 다른 배열과 마찬가지로 인덱싱 할 수 있습니다. 그런 다음 bts_action
1024 바이트 data
크기 의 구조체를 다음과 같이 할당합니다 .
size_t size = 1024;
struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size);
참조 : http://c2.com/cgi/wiki?StructHack
답변
코드가 유효한 C가 아닙니다 ( 참조 ). 리눅스 커널은 명백한 이유로 이식성에 전혀 관심이 없기 때문에 많은 비표준 코드를 사용합니다.
그들이하고있는 것은 배열 크기가 0 인 GCC 비표준 확장입니다. 표준 호환 프로그램이 작성 u8 data[];
되었을 것이고 그것은 똑같은 것을 의미했을 것입니다. Linux 커널의 작성자는 옵션이 드러나면 불필요하게 복잡하고 비표준으로 만드는 것을 좋아합니다.
이전 C 표준에서는 빈 배열로 구조체를 끝내는 것을 “구조 핵”이라고했습니다. 다른 사람들은 이미 다른 답변에서 그 목적을 설명했습니다. C90 표준에서 구조체 해킹은 정의되지 않은 동작이었으며 주로 C 컴파일러가 구조체 끝에 임의의 수의 패딩 바이트를 추가 할 수 있기 때문에 충돌을 일으킬 수 있습니다. 이러한 패딩 바이트는 구조체 끝에서 “해킹”하려는 데이터와 충돌 할 수 있습니다.
GCC는 초기에 비표준 확장을 만들어 정의되지 않은 동작에서 잘 정의 된 동작으로 변경했습니다. 그런 다음 C99 표준은이 개념을 채택했으며 모든 최신 C 프로그램은이 기능을 위험없이 사용할 수 있습니다. C99 / C11에서는 유연한 배열 멤버 로 알려져 있습니다.
답변
길이가 0 인 배열의 또 다른 용도는 컴파일 시간 구조체 오프셋 검사를 지원하기 위해 구조체 내부의 명명 된 레이블로 사용하는 것입니다.
(여러 캐시 라인에 걸쳐있는) 큰 구조체 정의가 경계를 넘는 시작 부분과 중간 부분 모두에서 캐시 라인 경계에 정렬되도록하고 싶다고 가정합니다.
struct example_large_s
{
u32 first; // align to CL
u32 data;
....
u64 *second; // align to second CL after the first one
....
};
코드에서 다음과 같은 GCC 확장을 사용하여 선언 할 수 있습니다.
__attribute__((aligned(CACHE_LINE_BYTES)))
그러나 여전히 이것이 런타임에 적용되는지 확인하고 싶습니다.
ASSERT (offsetof (example_large_s, first) == 0);
ASSERT (offsetof (example_large_s, second) == CACHE_LINE_BYTES);
이것은 단일 구조체에 대해 작동하지만 많은 구조체를 다루기 어렵고 각 구조체에는 정렬 할 멤버 이름이 다릅니다. 각 구조체의 첫 번째 멤버 이름을 찾아야하는 아래와 같은 코드를 얻을 수 있습니다.
assert (offsetof (one_struct, <name_of_first_member>) == 0);
assert (offsetof (one_struct, <name_of_second_member>) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, <name_of_first_member>) == 0);
assert (offsetof (another_struct, <name_of_second_member>) == CACHE_LINE_BYTES);
이런 식으로하는 대신, 일관된 이름을 가진 명명 된 레이블 역할을하지만 공간을 소비하지 않는 구조에서 길이가 0 인 배열을 선언 할 수 있습니다.
#define CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CACHE_LINE_BYTES)))
struct example_large_s
{
CACHE_LINE_ALIGN_MARK (cacheline0);
u32 first; // align to CL
u32 data;
....
CACHE_LINE_ALIGN_MARK (cacheline1);
u64 *second; // align to second CL after the first one
....
};
그러면 런타임 어설 션 코드가 훨씬 더 쉽게 유지 관리 할 수 있습니다.
assert (offsetof (one_struct, cacheline0) == 0);
assert (offsetof (one_struct, cacheline1) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, cacheline0) == 0);
assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES);