[c] 요소가 0 인 배열의 필요성은 무엇입니까?

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의 오프셋이 동일하지 않을 수 있습니다 .)


답변

이것은 실제로 GCC ( C90 )에 입니다.

구조체 해킹 이라고도합니다. .

그래서 다음에 저는 이렇게 말할 것입니다.

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고정 크기 헤더 ( typesize필드)와 가변 크기 data멤버 가있는 일부 데이터 패킷 일 것입니다 . 길이가 0 인 배열로 선언하면 다른 배열과 마찬가지로 인덱싱 할 수 있습니다. 그런 다음 bts_action1024 바이트 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);


답변