C / C ++에서 0 크기 배열을 정의하면 어떻게됩니까?


127

궁금한 점 int array[0];은 코드에서 길이가 0 인 배열 을 정의하면 실제로 어떻게됩니까 ? GCC는 전혀 불평하지 않습니다.

샘플 프로그램

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

설명

실제로 Darhazer의 의견에서 가변 길이처럼 지적되는 대신 길이가 0 인 배열이 이런 식으로 초기화되었는지 알아 내려고 노력하고 있습니다.

나는 야생으로 몇 가지 코드를 공개해야하기 때문에 나는 내가이 핸들 케이스에있는 경우 알아 내기 위해 노력하고, 그래서 이것은이다 SIZE로 정의 0정적으로 정의와 일부 코드에서 발생,int array[SIZE];

나는 실제로 GCC가 불평하지 않아서 내 질문에 이르렀다는 것에 놀랐다. 내가받은 답변에서 경고의 부족은 주로 새로운 [] 구문으로 업데이트되지 않은 오래된 코드를 지원하기 때문이라고 생각합니다.

나는 주로 오류에 대해 궁금해했기 때문에 Lundin의 대답을 올바른 것으로 태그하고 있습니다 (Nawaz는 처음이지만 완벽하지는 않았습니다). 내가 원하는 것을 정확하게.


51
@AlexanderCorwin : 불행하게도 C ++에서 정의되지 않은 동작, 비표준 확장 및 기타 이상이있는 경우 직접 시도하는 것은 지식의 경로가 아닙니다.
Benjamin Lindley

5
@JustinKirk 방금 테스트하고 작동하여 그에 갇히게되었습니다. 필자의 게시물에서 비난을 받았기 때문에 테스트와 테스트를 수행한다고해서 그것이 타당하고 합법적이라는 의미는 아니라는 것을 알게되었습니다. 따라서 자체 테스트는 때때로 유효하지 않습니다.
StormByte

2
@JustinKirk, Matthieu의 답변 을 참조하십시오 . 배열 크기가 템플릿 매개 변수 인 템플릿에서도 유용 할 수 있습니다. 문제의 예는 분명히 문맥에서 벗어납니다.
Mark Ransom

2
@ 저스틴 키 르크 : []파이썬이나 ""C 의 목적은 무엇입니까 ? 때로는 배열이 필요한 함수 나 매크로가 있지만이를 넣을 데이터가 없습니다.
dan04

15
"C / C ++"는 무엇입니까? 다음은 두 개의 분리 된 언어입니다
궤도의 밝기 경주

답변:


86

배열의 크기는 0 일 수 없습니다.

ISO 9899 : 2011 6.7.6.2 :

표현식이 상수 표현식 인 경우 0보다 큰 값을 가져야합니다.

위의 텍스트는 일반 배열 (1 항) 모두에 해당됩니다. VLA (가변 길이 배열)의 경우 표현식 값이 0보다 작거나 같으면 동작이 정의되지 않습니다 (문단 5). 이것은 C 표준의 규범적인 텍스트입니다. 컴파일러는 다르게 구현할 수 없습니다.

gcc -std=c99 -pedantic VLA가 아닌 경우에 대한 경고를 제공합니다.


34
"실제로 오류를 주어야한다"- "경고"와 "오류"의 구분은 표준에서 인식되지 않으며 ( "진단"만 언급) 컴파일이 중지되어야하는 유일한 상황 (예 : 실제 차이) 경고와 오류 사이]]에 #error지시문이 있습니다.
Random832

12
참고로, (C 또는 C ++) 표준은 일반적으로 컴파일러가 허용 해야하는 것만 허용 하고 허용하지 않는 것은 명시 하지 않습니다 . 어떤 경우에는 컴파일러가 "진단"을 발행해야한다고 말하지만 그 정도는 구체적입니다. 나머지는 컴파일러 공급 업체에 맡겨져 있습니다. 편집 : Random832가 말한 것.
mcmcc 2016 년

8
@Lundin "컴파일러는 길이가 0 인 배열을 포함하는 바이너리를 만들 수 없습니다." 표준은 전혀 아무 것도 말하지 않습니다. 크기가 0 인 상수 표현식을 갖는 배열을 포함하는 소스 코드가 제공 될 때 적어도 하나의 진단 메시지를 생성해야한다고 말합니다. 표준이 컴파일러가 바이너리를 빌드하는 것을 금지하는 유일한 상황은 #error전 처리기 지시어를 만나는 것 입니다.
Random832

5
@Lundin 모든 올바른 경우에 이진을 생성하면 # 1을 만족 시키며 잘못된 경우에 대한 이진 생성은 영향을 미치지 않습니다. # 3에는 경고를 인쇄하는 것으로 충분합니다. 표준은이 소스 코드의 동작을 정의하지 않기 때문에이 동작은 # 2와 관련이 없습니다.
Random832

13
@ 룬딘 : 요점은 당신의 진술이 잘못되었다는 것입니다. 적합한 컴파일러 진단이 실행되는 한 길이가 0 인 배열을 포함하는 바이너리를 빌드 할 수 있습니다.
Keith Thompson

85

표준에 따라 허용되지 않습니다.

그러나 C 컴파일러에서 이러한 선언을 FAM (Flexible Array Member ) 선언 으로 취급하는 것이 현재 관행입니다 .

C99 6.7.2.1, §16 : 특별한 경우로, 하나 이상의 명명 된 멤버가있는 구조의 마지막 요소는 불완전한 배열 유형을 가질 수 있습니다. 이것을 유연한 배열 구성원이라고합니다.

FAM의 표준 구문은 다음과 같습니다.

struct Array {
  size_t size;
  int content[];
};

아이디어는 다음과 같이 할당한다는 것입니다.

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

정적으로 사용할 수도 있습니다 (gcc 확장명).

Array a = { 3, { 1, 2, 3 } };

이것을 테일 패딩 구조 (이 용어는 C99 표준의 발행 이전) 또는 구조체 해킹 (Joe Wreschnig가 지적한 덕분)으로도 알려져 있습니다.

그러나이 구문은 최근 C99에서만 표준화 (및 효과 보장)되었습니다. 일정한 크기가 필요하기 전에.

  • 1 다소 이상했지만 휴대용 방법이었습니다.
  • 0 의도를 나타내는 데 더 좋았지 만 표준이 gcc를 포함한 일부 컴파일러의 확장으로 관련되고 지원되는 한 합법적이지는 않았습니다.

그러나 테일 패딩 실습은 스토리지를 사용할 수 있다는 점 (주의 malloc) 에 의존 하므로 일반적으로 스택 사용량에 적합하지 않습니다 .


@Lundin : VLA를 보지 못했습니다. 모든 크기는 컴파일 타임에 알려져 있습니다. 유연한 배열 용어에서 유래 gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html 및 자격 미상 int content[];까지 내가 이해 여기. 내가 예술의 C 용어에 너무 정통하지 않기 때문에 ... 내 추론이 올바른지 여부를 확인할 수 있습니까?
Matthieu M.

@MatthieuM .: C99 6.7.2.1, §16 : 특별한 경우로, 하나 이상의 명명 된 멤버가있는 구조의 마지막 요소는 불완전한 배열 유형을 가질 수 있습니다. 이것을 유연한 배열 구성원이라고합니다.
Christoph

이 관용구는 "struct hack" 이라는 이름으로도 알려져 있으며 "꼬리 패딩 구조"보다 해당 이름에 익숙한 사람들을 더 많이 만났습니다 (미래 ABI 호환성을위한 구조체 패딩에 대한 일반적인 참조를 제외하고는 들어 본 적이 없습니다) ) 또는 "유연한 배열 구성원"으로 C99에서 처음 들었습니다.

1
구조체 해킹에 1의 배열 크기를 사용하면 컴파일러가 엉뚱한 것을 피할 수 있지만 컴파일러 작성자는 사실상의 표준과 같은 사용법을 인정할만큼 훌륭하기 때문에 "휴대용"이었습니다. 제로 크기의 배열, 프로그래머가 단일 요소 배열을 미숙 한 대체물로 사용하는 것을 금지하지 않았으며, 표준이 요구하지 않더라도 프로그래머의 요구에 부응해야한다는 컴파일러 작성자의 역사적 태도는 컴파일러 작성자가 단일 요소 배열 일 때마다 쉽고 유용하게 최적화 foo[x]되었습니다 . foo[0]foo
supercat

1
@RobertSsupportsMonicaCellio : 답변에 명시 적으로 나와 있지만 끝에 있습니다. 나는 설명을 전면로드하여 시작부터 명확하게했습니다.
Matthieu M.

58

표준 C 및 C ++에서는 크기가 0 인 배열이 허용 되지 않습니다.

GCC를 사용하는 경우 -pedantic옵션으로 컴파일하십시오 . 다음과 같이 경고합니다 .

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

C ++의 경우 비슷한 경고가 표시됩니다.


9
Visual C ++ 2010에서 :error C2466: cannot allocate an array of constant size 0
Mark Ransom

4
-Werror는 단순히 모든 경고를 오류로 바꾸어 GCC 컴파일러의 잘못된 동작을 수정하지 않습니다.
Lundin

C ++ 빌더 2009은 제대로 오류를 제공합니다 :[BCC32 Error] test.c(3): E2021 Array must have at least one element
룬딘

1
대신 -pedantic -Werror, 당신은 또한 할 수 있습니다-pedantic-errors
Stephan Dollberg

3
크기가 0 인 배열은 크기가 0 인 것과 다릅니다 std::array. (제외 : VLA가 C ++에 포함되지 않은 것으로 간주되어 명시 적으로 거부 된 소스는 기억 나지 않습니다.)

27

그것은 완전히 불법이며 항상 그렇습니다. 그러나 많은 컴파일러는 오류를 알리지 않습니다. 왜 당신이 이것을하고 싶은지 모르겠습니다. 내가 아는 한 가지 사용법은 부울에서 컴파일 시간 오류를 발생시키는 것입니다.

char someCondition[ condition ];

경우 condition거짓이다, 나는 컴파일 타임 오류가 발생합니다. 그러나 컴파일러는 이것을 허용하기 때문에 다음을 사용했습니다.

char someCondition[ 2 * condition - 1 ];

이것은 1 또는 -1의 크기를 제공하며, -1의 크기를 허용하는 컴파일러를 찾지 못했습니다.


이것은 그것을 사용하는 흥미로운 해킹입니다.
Alex Koay

10
메타 프로그래밍의 일반적인 트릭이라고 생각합니다. 구현을 STATIC_ASSERT사용 하더라도 놀라지 않을 것입니다.
James Kanze

왜 그냥 :#if condition \n #error whatever \n #endif
Jerfov2

1
@ Jerfov2 전처리 시간에 조건을 알 수 없기 때문에 컴파일 시간 만
rmeador

9

이 주장에 gcc 온라인 문서의 전체 페이지 가 있다고 덧붙입니다.

인용문 :

GNU C에서는 길이가 0 인 배열이 허용됩니다.

ISO C90에서는 내용의 길이를 1로 지정해야합니다

3.0 이전의 GCC 버전에서는 길이가 0 인 배열을 마치 유연한 배열 인 것처럼 정적으로 초기화 할 수있었습니다. 유용한 경우 외에도 나중에 데이터를 손상시키는 상황에서 초기화를 허용했습니다.

그래서 당신은 할 수

int arr[0] = { 1 };

그리고 붐 :-)


내가 좋아하는 할 수있는 int a[0]다음, a[0] = 1 a[1] = 2??
Suraj Jain

2
@SurajJain 스택을 덮어 쓰려면 :-) C는 인덱스를 작성하고있는 배열의 크기를 확인하지 않으므로 a[100000] = 5운이 좋으면 운이 좋으면 앱이 단순히 중단됩니다. -)
xanatos

Int a [0]; 는 가변 배열 (제로 크기의 배열)을 의미합니다. 이제 어떻게 할당 할 수 있습니까?
Suraj Jain

@SurajJain "C가 인덱스를 확인하지 않고 쓰고있는 배열의 크기"의 어느 부분이 명확하지 않습니까? C에는 인덱스 검사가 없으므로 어레이가 끝난 후에 쓰고 컴퓨터를 충돌 시키거나 메모리의 소중한 비트를 덮어 쓸 수 있습니다. 따라서 0 요소의 배열이 있으면 0 요소의 끝 뒤에 쓸 수 있습니다.
xanatos


9

길이가 0 인 배열의 또 다른 용도는 가변 길이 객체 (pre-C99)를 만드는 것입니다. 제로 길이 배열이 되어 서로 다른가요 배열 [] 0 않고있다.

gcc doc 에서 인용 :

길이가 0 인 배열은 GNU C에서 허용됩니다. 실제로 가변 길이 객체의 헤더 인 구조의 마지막 요소로 매우 유용합니다.

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

ISO C99에서는 구문과 의미가 약간 다른 유연한 배열 멤버를 사용합니다.

  • 유연한 배열 멤버는 0없이 contents []로 작성됩니다.
  • 유연한 배열 구성원의 유형이 불완전하므로 sizeof 연산자가 적용되지 않을 수 있습니다.

실제 예는의 길이 제로의 배열 인 struct kdbus_itemkdbus.h (리눅스 커널 모듈).


2
IMHO, 표준이 길이가 0 인 배열을 금지 한 이유는 없었습니다. 그것은 구조의 구성원처럼 괜찮은 크기의 객체를 가질 수 있으며 void*산술 목적으로 간주했습니다 (따라서 크기가 0 인 객체에 대한 포인터를 추가하거나 빼는 것은 금지됩니다). Flexible Array 멤버는 대부분 크기가 0 인 배열보다 낫지 만, 뒤에 나오는 것에 대한 "구문"간접적 인 수준의 추가를 추가하지 않고도 사물을 별칭으로 만드는 일종의 "연합"의 역할을 할 수 있습니다 (예 : struct foo {unsigned char as_bytes[0]; int x,y; float z;}멤버에 액세스 할 수있는 경우 x.. z...
supercat

... 직접 예 말할 필요없이 myStruct.asFoo.x이와 같이 제작 등 또한 IIRC는 어떠한 노력에서 C의 울음 소리가 구조체 내가요 어레이 부재를 포함하는 것이 불가능 알려진 길이의 여러 다른가요 어레이 구성원을 포함하는 구조를 가질 함유량.
supercat

@supercat 좋은 이유는 외부 배열 범위에 액세스하는 규칙의 무결성을 유지하는 것입니다. 구조체의 마지막 멤버 인 C99 가변 배열 멤버 는 GCC 크기가 0 인 배열과 동일한 효과를 얻지 만 다른 규칙에 특별한 경우를 추가하지 않아도됩니다. IMHO sizeof x->contentsgcc에서 0을 반환하는 것과는 달리 ISO C의 오류 인 개선 사항입니다 . 구조체 멤버가 아닌 크기가 0 인 배열에는 여러 가지 다른 문제가 있습니다.
MM

@ MM : 크기가 0 인 객체에 대해 두 개의 동일한 포인터를 빼는 것이 0을 산출하는 것으로 정의되고 (모든 크기의 객체에 대해 동일한 포인터를 빼는 것과 같이) 크기가 0 인 객체에 대해 불균등 한 포인터를 빼는 것이 항복으로 정의되면 어떤 문제가 발생합니까 불특정 가치? 표준이 구현에 따라 FAM을 포함하는 구조체가 다른 구조체 내에 포함되도록 허용하는 경우 후자의 구조체의 다음 요소가 FAM과 동일한 요소 유형을 가진 배열이거나 이러한 배열로 시작하는 구조체 그리고 그것을 제공했습니다 ...
supercat

... FAM은 배열을 앨리어싱으로 인식합니다 (정렬 규칙으로 인해 배열이 다른 오프셋에 도달하면 진단이 필요합니다). 매우 유용했을 것입니다. 따라서 일반적인 형식의 구조에 대한 포인터를 허용 struct {int n; THING dat[];}하고 정적 또는 자동 지속 시간의 작업을 수행 할 수 있는 방법을 갖는 좋은 방법은 없습니다 .
supercat

6

구조체 내에서 크기가 0 인 배열 선언은 허용되는 경우 유용하고 의미론이 (1) 정렬을 강제하지만 공간을 할당하지 않고 (2) 배열을 인덱싱하는 것은 정의 된 동작으로 간주됩니다. 결과 포인터가 구조체와 동일한 메모리 블록 내에있는 경우 이러한 동작은 어떤 C 표준에서도 허용되지 않았지만 일부 구형 컴파일러는 빈 대괄호로 불완전한 배열 선언을 허용하는 컴파일러의 표준이되기 전에 허용했습니다.

1 크기의 배열을 사용하여 일반적으로 구현되는 구조체 해킹은 복잡하며 컴파일러가 그것을 깨뜨리는 것을 요구하지 않는다고 생각합니다. 예를 들어, 컴파일러가을 볼 경우으로 간주 int a[1]할 수있는 권한이 있다고 생각 a[i]합니다 a[0]. 누군가가 다음과 같은 것을 통해 구조체 해킹의 정렬 문제를 해결하려고하면

typedef 구조체 {
  uint32_t 크기;
  uint8_t 데이터 [4]; // 패딩이 구조체의 크기를 버리지 않도록 4를 사용하십시오.
}

컴파일러는 영리하고 배열 크기가 실제로 4라고 가정합니다.

; 쓰여진대로
  foo = myStruct-> 데이터 [i];
; 해석 된대로 (little-endian 하드웨어 가정)
  foo = (((* (uint32_t *) myStruct-> data) >> (i << 3)) & 0xFF;

이러한 최적화는 특히 myStruct->data와 동일한 작업에서 레지스터에로드 될 수있는 경우에 합리적 일 수 있습니다 myStruct->size. 나는 표준에서 그러한 최적화를 금지하는 것은 아무것도 모르지만 물론 네 번째 요소를 넘어서서 액세스 할 수있는 코드를 깨뜨릴 것입니다.


1
가요 어레이 부재 구조체 해킹 합법적 버전으로 C99에 첨가
MM

표준은 다른 배열 구성원에 대한 액세스가 충돌하지 않으므로 최적화가 불가능하다고 말합니다.
Ben Voigt

@ BenVoigt : C 언어 표준은 바이트 쓰기와 단어 포함을 동시에 읽는 효과를 지정하지 않지만 프로세서의 99.9 %는 쓰기가 성공하고 단어에 새 버전 또는 이전 버전이 포함되도록 지정합니다 다른 바이트의 변경되지 않은 내용과 함께 바이트. 컴파일러가 이러한 프로세서를 대상으로하는 경우 충돌은 무엇입니까?
supercat

@supercat : C 언어 표준은 두 개의 다른 배열 요소에 대한 동시 쓰기가 충돌하지 않도록 보장합니다. 따라서 (쓰기 중 읽기) 제대로 작동한다는 귀하의 주장으로는 충분하지 않습니다.
벤 Voigt

@ BenVoigt : 코드 조각이 배열 요소 0, 1 및 2에 어떤 순서로 쓰려면 4 개의 요소를 모두 길게 읽고 3 개를 수정하고 4 개를 모두 다시 쓸 수는 없지만 I 4 개 모두를 길게 읽고, 3 개를 수정하고, 하위 16 비트를 짧게, 비트 16-23을 바이트로 다시 쓸 수 있다고 생각하십시오. 동의하지 않습니까? 그리고 배열의 요소를 읽는 데 필요한 코드는 단순히 요소를 길게 읽고 사용할 수 있습니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.