C 배열의 길이가 0이 아닌 이유는 무엇입니까?


13

C11 표준에 따르면 크기와 가변 길이가 모두 "0보다 큰 값을 갖습니다." 길이가 0을 허용하지 않는 이유는 무엇입니까?

특히 가변 길이 배열의 경우 가끔씩 0의 크기를 갖는 것이 완벽합니다. 또한 정적 배열의 크기가 매크로 또는 빌드 구성 옵션 인 경우에도 유용합니다.

흥미롭게도 GCC (및 clang)는 길이가 0 인 배열을 허용하는 확장을 제공합니다. Java는 길이가 0 인 배열도 허용합니다.


7
stackoverflow.com/q/8625572 ... "길이가 0 인 배열은 각 개체에 고유 한 주소가 있어야한다는 요구 사항을 조정하기가 까다 롭고 혼동됩니다."
Robert Harvey

3
@RobertHarvey : 감안할는 struct { int p[1],q[1]; } foo; int *pp = p+1;, pp합법적 인 포인터가 될 것이지만, *pp고유 한 주소가없는 것입니다. 길이가 0 인 배열에서 동일한 논리를 유지할 수없는 이유는 무엇입니까? int q[0]; 구조 내에서 주어진 경우 , 위 qp+1예 와 같은 유효성을 갖는 주소를 참조 한다고 가정 하십시오 .
supercat

@DocBrown C11 표준 6.7.6.2.5에서 VLA의 크기를 결정하는 데 사용되는 표현에 대해 말하면서 "… 평가할 때마다 0보다 큰 값을 가져야합니다." 나는 C99에 대해 모른다. (그리고 그것들이 바뀔 것이 이상해 보인다) 길이가 0이 아닌 것처럼 들린다.
케빈 콕스

@KevinCox : 무료 온라인 버전의 C11 표준 (또는 해당 부분)이 있습니까?
Doc Brown

최종 버전은 무료로 제공되지 않지만 초안을 다운로드 할 수 있습니다. 사용 가능한 마지막 초안은 open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf 입니다.
케빈 콕스

답변:


11

내가 베풀어야 할 문제는 C 배열이 할당 된 메모리 덩어리의 시작을 가리키는 포인터라는 것입니다. 크기가 0이면 포인터가 없음을 의미합니다. 당신은 아무것도 가질 수 없으므로 임의의 것이 선택되어야 할 것입니다. null길이가 0 인 배열은 null 포인터처럼 보이기 때문에을 사용할 수 없습니다 . 그리고 그 시점에서 모든 구현마다 다른 임의의 동작을 선택하여 혼란을 초래합니다.



8
@delnan : 글쎄, 그것에 대해 pedantic하고 싶다면 배열과 포인터 산술은 포인터가 배열에 액세스하거나 배열을 시뮬레이션하는 데 편리하게 사용할 수 있도록 정의됩니다. 다시 말해, C에서 동등한 포인터 산술 및 배열 인덱싱입니다. 그러나 결과는 어쨌든 동일합니다. 배열의 길이가 0이면 여전히 아무것도 가리 키지 않습니다.
Robert Harvey

3
@RobertHarvey 모두 사실이지만 닫는 단어 (및 전체 답변)는 그러한 배열을 설명하는 혼란스럽고 혼란스러운 방법처럼 보입니다 ( 이 답변이 "할당 된 메모리 덩어리" 라고 생각 하는 것입니까?) sizeof0, 그리고 그것이 어떻게 문제를 일으키는 지. 간결함이나 명료 함을 잃지 않고 적절한 개념과 용어를 사용하여 설명 할 수 있습니다. 배열과 포인터를 혼합하면 배열을 확산시킬 위험이 있습니다.

2
" 0 길이 배열은 널 포인터처럼 보일 것이기 때문에 널을 사용할 수 없습니다. 실제로는 델파이가하는 것입니다. 빈 다이나 레이 및 빈 긴 문자열은 기술적으로 널 포인터입니다.
JensG

3
-1, 나는 @delnan으로 가득합니다. 이것은 특히 길이가 0 인 배열 개념을 지원하는 일부 주요 컴파일러에 대해 OP가 작성한 내용과 관련하여 아무것도 설명하지 않습니다. 나는 길이가 0 인 배열이 C에서 "혼돈을 초래하지 않는"구현 방식으로 제공 될 수 있다고 확신한다.
Doc Brown

6

배열이 일반적으로 메모리에 어떻게 배치되는지 살펴 보겠습니다.

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

arr첫 번째 요소의 주소를 저장 하는 별도의 개체 이름은 없습니다 . 배열이 표현식에 나타나면 C 는 필요에 따라 첫 번째 요소의 주소를 계산 합니다.

그래서,하자이 생각 : 0 요소의 배열이없는 것이 더 저장 이 따로 세트를 배열 주소 계산 거기에 아무것도 의미 에서을 (다른 방법을 넣어은 식별자에 대한 객체의 매핑이 없습니다). " int메모리를 차지하지 않는 변수 를 만들고 싶습니다 ." 무의미한 작업입니다.

편집하다

Java 배열은 C 및 C ++ 배열과 완전히 다른 동물입니다. 그것들은 기본 유형이 아니라에서 파생 된 참조 유형입니다 Object.

편집 2

아래 주석에서 제기 된 요점- "0보다 큰"제한 조건은 크기가 상수 표현식을 통해 지정된 배열에만 적용됩니다 . VLA의 길이는 0이 될 수 있습니다. 값이 0이 아닌 상수 식으로 VLA를 선언하는 것은 제약 조건 위반이 아니지만 정의되지 않은 동작을 호출합니다.

VLA는 일반 배열 다른 동물 이며, 구현시 0 크기를 허용 할 수 있습니다 . static프로그램을 시작하기 전에 이러한 오브젝트의 크기를 알아야하므로 선언 할 수 없거나 파일 범위에 있을 수 없습니다 .

C11에서 VLA를 지원하기 위해 구현이 필요하지 않은 것도 가치가 없습니다.


3
미안하지만 IMHO는 Telastyn과 마찬가지로 요점을 놓치고 있습니다. 길이가 0 인 배열은 많은 의미를 가질 수 있으며 OP가 우리에게 말한 것과 같은 기존 구현은 그것이 가능하다는 것을 보여줍니다.
Doc Brown

@DocBrown : 먼저, 언어 표준이 왜 그것을 허용하지 않는지에 대해 이야기하고있었습니다. 둘째, 정직하게 생각할 수 없기 때문에 0 길이 배열이 의미가있는 예를 원합니다. 가장 가능성있는 구현은로 취급 T a[0]하는 T *a것이지만 왜 그렇게 사용하지 T *a않습니까?
John Bode

죄송하지만, 표준이 이것을 금지하는 이유에 대한 "이론적 추론"은 구매하지 않습니다. 주소를 실제로 쉽게 계산하는 방법에 대한 답변을 읽으십시오. 그리고 Robert Harveys의 첫 번째 의견 아래 링크를 따라 질문에 답하고 두 번째 답변을 읽으십시오. 유용한 예가 있습니다.
Doc Brown

@DocBrown : 아. struct해킹. 나는 그것을 개인적으로 사용한 적이 없다. 다양한 크기의 struct유형 이 필요한 문제를 해결하지 못했습니다 .
John Bode

2
C99 이후 C는 AFAIK를 잊지 않고 가변 길이 배열을 허용합니다. 그리고 배열 크기가 매개 변수 인 경우, 값 0을 특수한 경우로 취급하지 않아도 많은 프로그램을 더 간단하게 만들 수 있습니다.
Doc Brown

2

일반적으로 제로 (사실 변수) 크기 배열이 런타임에 크기를 알기를 원합니다. 그런 다음 a로 묶고 다음 과 같이 유연한 배열 멤버를struct 사용하십시오 .

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

분명히 유연한 배열 구성원은 마지막 배열이어야하며 struct이전에 무언가가 있어야합니다. 종종 이는 유연한 배열 구성원의 실제 런타임 점유 길이와 관련이 있습니다.

물론 다음을 할당합니다.

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

AFAIK, 이것은 C99에서 이미 가능했으며 매우 유용합니다.

BTW, 유연한 배열 구성원은 C ++에 존재하지 않습니다 (구성 및 파괴시기와 방법을 정의하기 어렵 기 때문에). 그러나 미래 std :: dynarray 참조


아시다시피, 그들은 사소한 유형으로 제한 될 수 있으며 어려움이 없습니다.
중복 제거기

2

표현식 type name[count]이 일부 함수로 작성된 경우 C 컴파일러에 스택 프레임 sizeof(type)*count바이트 를 할당 하고 배열의 첫 번째 요소 주소를 계산하도록 지시 합니다.

표현식 type name[count]이 모든 함수 외부에 작성되고 정의를 정의하는 경우 C 컴파일러에게 데이터 세그먼트 sizeof(type)*count바이트 를 할당 하고 배열의 첫 번째 요소 주소를 계산하도록 지시 합니다.

name실제로 배열의 첫 번째 요소의 주소를 저장하는 상수 객체이며 일부 메모리의 주소를 저장하는 모든 객체를 포인터라고하므로 name배열이 아닌 포인터로 취급하는 이유 입니다. C의 배열은 포인터를 통해서만 액세스 할 수 있습니다.

count0으로 평가되는 상수 식인 경우 C 컴파일러에 스택 프레임 또는 데이터 세그먼트에 0 바이트를 할당하고 배열의 첫 번째 요소 주소를 반환하도록 지시하지만이 작업의 문제점은 첫 번째 요소라는 것입니다. 길이가 0 인 배열이 존재하지 않으며 존재하지 않는 주소를 계산할 수 없습니다.

이것은 요소 번호에 합리적입니다. 길이 배열에 count+1존재하지 않으므로 count이것이 C 컴파일러가 길이가 0 인 배열을 함수 안팎의 변수로 정의하는 것을 금지하는 이유입니다. 당시 내용은 무엇 name입니까? 어떤 주소가 name정확히 저장됩니까?

경우 p포인터가 그 표현은 p[n]동일하다*(p + n)

올바른 표현 별표 * 수단에 의해 지시 된 메모리 액세스 포인터의 참조 해제 조작 인 경우 p + n, 그 주소에 저장되어있는 메모리 나 액세스 p + n, p + n포인터의 표현을 그것의 주소를 얻어 p,이 어드레스 번호를 추가 n곱 포인터 타입의 크기 p.

주소와 번호를 추가 할 수 있습니까?

예, 주소는 일반적으로 16 진수 표기법으로 표시되는 부호없는 정수이므로 가능합니다.


많은 컴파일러가 표준 이전에 크기가 0 인 배열 선언을 허용하는 데 사용했으며, 확장으로 이러한 선언을 계속 허용하는 경우가 많습니다. 이러한 선언은 크기의 객체에 연결된 주소 N가 있음을 인식하면 문제가되지 않습니다 N+1. 첫 번째 N는 고유 바이트를 식별하고 마지막 N은 각 바이트가 해당 바이트 중 하나를 지난 것입니다. 이러한 정의 N는 0이 되는 퇴화 사례에서도 잘 작동 합니다.
supercat

1

메모리 주소에 대한 포인터를 원하면 선언하십시오. 배열은 실제로 예약 한 메모리 청크를 가리 킵니다. 함수에 전달 될 때 포인터가 포인터로 붕괴되지만, 가리키는 메모리가 힙에 있으면 아무런 문제가 없습니다. 크기가 0 인 배열을 선언 할 이유가 없습니다.


2
일반적으로 매크로의 결과로 또는 동적 데이터로 가변 길이 배열을 선언 할 때 직접 수행하지는 않습니다.
케빈 콕스

배열은 절대 가리 키지 않습니다. 포인터를 포함 할 수 있으며 대부분의 상황에서 실제로 첫 번째 요소에 대한 포인터를 사용하지만 다른 이야기입니다.
중복 제거기

1
배열 이름은 배열에 포함 된 메모리에 대한 상수 포인터입니다.
ncmathsadist

1
아니요, 배열 이름 대부분의 상황에서 첫 번째 요소 대한 포인터로 감소 합니다. 차이점은 종종 중요합니다.
중복 제거기

1

원래의 C89 시대부터 C 표준이 정의되지 않은 동작을 가지고 있다고 명시했을 때의 의미는 "특정 대상 플랫폼에서 구현이 의도 한 목적에 가장 적합한 것을 수행하는 것은 무엇입니까?"라는 의미였습니다. 이 표준의 저자는 어떤 행동이 특정 목적에 가장 적합한 지 추측하려고하지 않았습니다. VLA 확장을 사용하는 기존 C89 구현은 크기가 0 일 때 다르지만 논리적으로 동작했을 수 있습니다 (예 : 일부는 배열을 주소 표현식으로 처리하여 NULL을 생성하는 반면 다른 일부는이를 주소와 동일한 주소 표현식으로 처리 할 수 ​​있음) 다른 임의의 변수이지만 트래핑없이 안전하게 0을 추가 할 수 있습니다). 코드가 그러한 다른 행동에 의존했을 수 있다면 표준 작성자는

표준 구현 자들은 어떤 구현이 무엇을하는지 추측하거나 어떤 행동이 다른 행동보다 우월한 것으로 간주되어야한다고 제안하기보다는 구현 자들이 그 사건을 가장 잘 처리 할 수 있도록 판단 할 수 있도록 허용 했다. 장면 뒤에서 malloc ()을 사용하는 구현은 배열 주소를 NULL로 처리 할 수 ​​있고 (크기-제로 malloc이 널을 생성하는 경우) 스택 주소 계산을 사용하는 구현은 다른 변수의 주소와 일치하는 포인터를 생성 할 수 있으며 일부 다른 구현에서는 다른 것들. 필자는 컴파일러 작성자가 제로 크기의 코너 케이스를 의도적으로 쓸모없는 방식으로 작동시키기를 기대하지는 않았다고 생각합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.