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


122

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

요소가없는 데이터 배열의 필요성과 목적은 무엇입니까?


길이0 인 배열 또는 struct-hack 태그 가 있어야하는지 잘 모르겠습니다 ...
hippietrail

@hippietrail, 왜냐하면 누군가이 구조체가 무엇인지 물어 보면 "유연한 배열 구성원"이라는 것을 알지 못하기 때문입니다. 그렇게했다면 쉽게 답을 찾을 수 있었을 것입니다. 그렇지 않기 때문에 질문에 태그를 지정할 수 없습니다. 그렇기 때문에 우리는 그러한 태그가 없습니다.
Shahbaz

10
재 개설에 투표하십시오. 다른 게시물은 길이가 0 인 비표준 "구조 해킹"과 잘 정의 된 C99 기능의 유연한 배열 구성원의 조합을 다루지 않기 때문에 이것이 중복이 아니라는 데 동의합니다. 또한 Linux 커널의 모호한 코드에 대한 정보를 제공하는 것이 C 프로그래밍 커뮤니티에 항상 유익하다고 생각합니다. 주로 많은 사람들이 Linux 커널이 알려지지 않은 이유로 일종의 최첨단 C 코드라는 인상을 받기 때문입니다. 실제로는 C 캐논으로 간주해서는 안되는 비표준 익스플로잇이 넘쳐나는 끔찍한 혼란입니다.
Lundin 2013

5
중복되지 않음-누군가가 불필요하게 질문을 닫는 것을 처음 본 것은 아닙니다. 또한이 질문이 SO 지식 기반에 추가되었다고 생각합니다.
Aniket Inge 2013

답변:


139

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


명확하게 말하면 질문의 원본 코드는 여전히 C99 (또는 C11)에서 표준이 아니며 여전히 해킹으로 간주됩니다. C99 표준화는 배열 바인딩을 생략해야합니다.
MM

무엇입니까 [0, extra)?
SS Anne


36

이것은 실제로 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 객체를 얼마든지 만들 수 있습니다.


7

아이디어는 구조체의 끝에 가변 크기 배열을 허용하는 것입니다. 아마도 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


2
@Aniket : 나는 아이디어 가 어디서 왔는지 완전히 확신하지 못합니다 .
sheu

C ++에서는 예, C에서는 필요하지 않습니다.
amc 2013

2
@sheu, 그것은 당신의 글쓰기 스타일 malloc이 당신 자신을 여러 번 반복하게 만들고 action변경 유형이 변경되면 여러 번 수정해야 한다는 사실에서 비롯됩니다 . 다음 두 가지를 직접 비교해 보면 알게 될 것입니다. struct some_thing *variable = (struct some_thing *)malloc(10 * sizeof(struct some_thing));vs. struct some_thing *variable = malloc(10 * sizeof(*variable));두 번째는 더 짧고 깔끔하며 변경하기가 더 쉽습니다.
Shahbaz 2013

5

코드가 유효한 C가 아닙니다 ( 참조 ). 리눅스 커널은 명백한 이유로 이식성에 전혀 관심이 없기 때문에 많은 비표준 코드를 사용합니다.

그들이하고있는 것은 배열 크기가 0 인 GCC 비표준 확장입니다. 표준 호환 프로그램이 작성 u8 data[];되었을 것이고 그것은 똑같은 것을 의미했을 것입니다. Linux 커널의 작성자는 옵션이 드러나면 불필요하게 복잡하고 비표준으로 만드는 것을 좋아합니다.

이전 C 표준에서는 빈 배열로 구조체를 끝내는 것을 "구조 핵"이라고했습니다. 다른 사람들은 이미 다른 답변에서 그 목적을 설명했습니다. C90 표준에서 구조체 해킹은 정의되지 않은 동작이었으며 주로 C 컴파일러가 구조체 끝에 임의의 수의 패딩 바이트를 추가 할 수 있기 때문에 충돌을 일으킬 수 있습니다. 이러한 패딩 바이트는 구조체 끝에서 "해킹"하려는 데이터와 충돌 할 수 있습니다.

GCC는 초기에 비표준 확장을 만들어 정의되지 않은 동작에서 잘 정의 된 동작으로 변경했습니다. 그런 다음 C99 표준은이 개념을 채택했으며 모든 최신 C 프로그램은이 기능을 위험없이 사용할 수 있습니다. C99 / C11에서는 유연한 배열 멤버 로 알려져 있습니다.


3
나는 "리눅스 커널은 이식성과 관련이 없다"고 의심한다. 아마도 다른 컴파일러에 대한 이식성을 의미했을까요? gcc의 기능과 상당히 얽혀있는 것은 사실입니다.
Shahbaz

3
그럼에도 불구하고이 특정 코드는 주류 코드가 아니며 작성자가 그다지주의를 기울이지 않았기 때문에 생략되었을 것입니다. 라이센스에는 일부 텍사스 인스트루먼트 드라이버에 대한 내용이 나와 있으므로 커널의 핵심 프로그래머가 관심을 기울이지 않았을 것입니다. 나는 커널 개발자들이 새로운 표준이나 새로운 최적화에 따라 지속적으로 오래된 코드를 업데이트하고 있다고 확신합니다. 모든 것이 업데이트되었는지 확인하기에는 너무 큽니다!
Shahbaz

1
@Shahbaz "명백한"부분은 다른 운영 시스템으로의 이식성을 의미했습니다. 그러나 그들은 다른 컴파일러에 대한 이식성에 대해 망설이지 않는 것 같습니다. 그들은 너무 많은 GCC 확장을 사용하여 Linux가 다른 컴파일러로 이식되지 않을 것입니다.
Lundin

3
@Shahbaz Texas Instruments라는 레이블이 붙은 모든 경우에 대해 TI 자체는 다양한 TI 칩에 대한 앱 노트에서 지금까지 본 것 중 가장 쓸모없고 엉터리이며 순진한 C 코드를 생성하는 것으로 유명합니다. 코드가 TI에서 비롯된 경우 유용한 내용을 해석 할 가능성에 대한 모든 베팅이 해제됩니다.
Lundin

4
리눅스와 gcc는 뗄래야 뗄 수없는 것이 사실입니다. Linux 커널도 이해하기 매우 어렵습니다 (대부분 OS가 어쨌든 복잡하기 때문입니다). 하지만 내 요점은 "리눅스 커널의 작성자는 옵션이 타사의 나쁜 코딩 관행으로 인해 자신을 드러내면 불필요하게 복잡하고 비표준적인 것을 만드는 것을 좋아한다"고 말하는 것은 좋지 않다는 것입니다. .
Shahbaz

1

길이가 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);

흥미로운 아이디어. 표준에서 길이가 0 인 배열은 허용되지 않으므로 이것은 컴파일러에 특정한 것입니다. 또한 구조체 정의에서 길이가 0 인 배열의 동작에 대한 gcc의 정의를 인용하여 최소한 선언 전후에 패딩을 도입 할 수 있는지 여부를 표시하는 것이 좋습니다.
Shahbaz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.