C 배열이 길이를 추적하지 않는 이유는 무엇입니까?


77

배열과 함께 배열의 길이를 명시 적으로 저장하지 않는 이유는 무엇입니까 C?

내가 보는 방식으로, 그렇게해야 할 압도적 인 이유 있지만 표준 (C89)을 지원하는 것은별로 많지 않습니다. 예를 들어 :

  1. 버퍼에 길이가 있으면 버퍼 오버런을 방지 할 수 있습니다.
  2. Java 스타일 arr.length은 명확하며 프로그래머 int가 여러 배열을 처리하는 경우 스택에서 많은 수를 유지하지 않아도 됩니다.
  3. 기능 매개 변수가 더욱 강력 해집니다.

그러나 아마도 가장 동기 부여가되는 이유는 일반적으로 길이를 유지하지 않고 공간이 절약되지 않기 때문입니다. 대부분의 어레이 사용에는 동적 할당이 포함된다고 말하고 싶습니다. 사실, 사람들이 스택에 할당 된 배열을 사용하는 경우가있을 수 있지만, 이는 한 번의 함수 호출 *입니다. 스택은 4 또는 8 바이트를 추가로 처리 할 수 ​​있습니다.

힙 관리자는 어쨌든 동적으로 할당 된 배열에 의해 사용 된 여유 블록 크기를 추적해야하기 때문에 해당 정보를 사용 가능하게 만들고 컴파일 타임에 검사 한 추가 규칙을 추가하여 길이를 명시 적으로 조작 할 수없는 경우 발로 자신을 촬영하는 것을 좋아합니다).

다른 측면에서 생각할 수있는 유일한 것은 길이 추적으로 컴파일러를 더 단순하게 만들지 않았지만 그렇게 간단 하지는 않다는 것입니다.

* 기술적으로는 자동 저장 장치가있는 배열로 일종의 재귀 함수를 작성할 수 있으며, 길이를 저장하는이 (매우 정교한) 경우 실제로 더 많은 공간 사용이 발생할 수 있습니다.


6
C가 매개 변수 및 반환 값 유형으로 구조체를 사용하여 포함 할 때 길이가 배열이나 배열 또는 배열에 대한 포인터가있는 "벡터"(또는 모든 이름)에 대한 구문 설탕을 포함해야한다고 주장 할 수 있다고 생각합니다. . 이 공통 구문에 대한 언어 수준의 지원 (단일 구조체가 아닌 별도의 인수로 전달되는 경우)도 수많은 버그를 줄이고 표준 라이브러리도 단순화했습니다.
hyde


34
다른 모든 대답에는 흥미로운 점이 있지만 결론은 C가 작성되었으므로 어셈블리 언어 프로그래머가 코드를 쉽게 작성하고 이식 할 수있게하는 것입니다. 그것을 염두에두고, 배열과 함께 배열 된 길이를 자동으로 저장하는 것은 성가신 것이지 단점이 아닙니다 (다른 멋진 사탕 코팅 욕구와 마찬가지로). 이러한 기능은 오늘날에는 멋져 보이지만, 그 당시에는 프로그램이나 데이터의 한 바이트를 시스템에 압축하는 것이 종종 힘들었습니다. 낭비되는 메모리 사용은 C의 채택을 심각하게 제한했을 것입니다.
덩크

6
귀하의 답변의 실제 부분은 이미 여러 차례 답변을 받았지만 다른 점을 추출 할 수 있습니다. " malloc()ed 영역 의 크기를 휴대용 방식으로 요청할 수없는 이유는 무엇 입니까?" 그것은 여러 번 궁금해하는 것입니다.
glglgl

5
재개 투표. "K & R이 생각하지 않았다"고해도 어딘가에 이유가 있습니다.
Telastyn

답변:


106

C 배열은 배열 길이가 정적 속성이므로 길이를 추적합니다.

int xs[42];  /* a 42-element array */

일반적으로이 길이를 쿼리 할 수는 없지만 어쨌든 정적이기 때문에 필요하지 않습니다 XS_LENGTH. 길이에 대한 매크로 를 선언하면 끝입니다.

더 중요한 문제는 C 배열이 함수로 전달 될 때 포인터로 암시 적으로 저하된다는 것입니다. 이것은 의미가 있고, 좋은 저수준 트릭을 허용하지만 배열 길이에 대한 정보를 잃습니다. 따라서 C가 포인터에 대한 암시 적 저하로 설계된 이유는 더 좋은 질문입니다.

또 다른 문제는 포인터가 메모리 주소 자체를 제외하고 스토리지가 필요 없다는 것입니다. C를 사용하면 정수를 포인터, 다른 포인터를 캐스트하고 포인터를 배열처럼 취급 할 수 있습니다. 이 작업을 수행하는 동안 C는 일부 배열 길이를 존재하기에 충분하지는 않지만 Spiderman 모토를 신뢰하는 것 같습니다. 강력한 힘으로 프로그래머는 길이와 오버플로를 추적하는 큰 책임을 희망적으로 이행 할 것입니다.


13
내가 실수하지 않으면 C 컴파일러 가 정적 배열 길이를 추적 한다는 것을 의미한다고 생각합니다 . 그러나 이것은 포인터를 얻는 함수에는 좋지 않습니다.
VF1

25
@ VF1 예. 그러나 중요한 것은 배열과 포인터가 C에서 다른 것입니다 . 컴파일러 확장을 사용하지 않는다고 가정하면 일반적으로 배열 자체를 함수에 전달할 수는 없지만 포인터를 전달하고 포인터를 배열처럼 인덱싱 할 수 있습니다. 포인터에 길이가 붙어 있지 않다고 효과적으로 불평하고 있습니다. 배열을 함수 인수로 전달할 수 없거나 배열이 암시 적으로 포인터로 성능이 저하된다고 불평해야합니다.
amon

37
"보통이 길이를 쿼리 할 수 ​​없습니다"– 실제로는 sizeof 연산자 일 수 있습니다. sizeof (xs)는 int의 길이가 4 바이트라고 가정하면 168을 반환합니다. 42를 얻으려면 : sizeof (xs) / sizeof (int)
tcrosley

15
@tcrosley 배열 선언의 범위 내에서만 작동합니다. xs를 다른 함수에 매개 변수로 전달한 다음 sizeof (xs)가 제공하는 것을 확인하십시오 ...
Gwyn Evans

26
@GwynEvans 다시 : 포인터는 배열이 아닙니다. 따라서 "배열을 다른 함수에 매개 변수로 전달"하면 배열이 아닌 포인터가 전달됩니다. C의 설계로 인해 배열의 범위를 벗어날 수 없기 때문에 배열이 sizeof(xs)어디에 있는지 xs다른 범위에서 다른 것으로 주장한다는 것은 명백히 잘못된 것입니다. 배열이 어디에 있는가 포인터가있는 것과 다른 경우 sizeof(xs), 사과를 오렌지와 비교 하기 때문에 놀라운 것은 아닙니다 . xssizeof(xs)xs
amon

38

이것은 당시 사용 가능한 컴퓨터와 관련이있었습니다. 컴파일 된 프로그램은 제한된 리소스 컴퓨터에서 실행되어야 할뿐만 아니라, 더 중요한 것은 컴파일러 자체가 이러한 시스템에서 실행되어야했습니다. Thompson은 C를 개발할 때 8k RAM을 가진 PDP-7을 사용하고있었습니다. 실제 기계 코드에 대한 즉각적인 아날로그가없는 복잡한 언어 기능은 단순히 언어에 포함되지 않았습니다.

C이력을 주의 깊게 읽으면 위의 내용을 더 잘 이해할 수 있지만 기계 제한의 결과는 아닙니다.

또한 언어 (C)는 몇 가지 기본 규칙과 규칙을 사용하여 런타임에 길이가 변하는 벡터와 같은 중요한 개념을 설명 할 수있는 상당한 힘을 보여줍니다. ... C의 접근 방식을 거의 동시대 언어 인 Algol 68과 Pascal [Jensen 74]의 접근 방식과 비교하는 것이 흥미 롭습니다. Algol 68의 배열은 고정 범위를 갖거나 '유연성'입니다 : 언어 정의와 유연한 배열을 수용하기 위해 컴파일러에서 상당한 메커니즘이 필요합니다 (모든 컴파일러가 완전히 구현하지는 않습니다). 배열과 문자열, 그리고 이것은 [Kernighan 81]을 제한하는 것으로 판명되었습니다.

C 배열은 본질적으로 더 강력합니다. 이것에 경계를 추가하면 프로그래머가 사용할 수있는 것이 제한됩니다. 이러한 제한은 프로그래머에게 유용 할 수 있지만 반드시 제한적입니다.


4
이것은 원래의 질문에 거의 못 박힌다. 그리고 C가 운영 체제를 작성하는 데 매력적으로 만들기 위해 프로그래머가 수행 한 작업을 확인할 때 C가 의도적으로 "가벼운 터치"를 유지하고 있다는 사실.
ClickRick

5
훌륭한 연결, 그들은 또한 구분자를 사용하기 위해 문자열의 길이를 저장하는 것을 명시 적으로 변경 to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator했습니다 :-)
Voo

5
종료되지 않은 배열은 C의 베어 메탈 방식에도 적합합니다. K & R C 서적 은 언어 튜토리얼, 참조 및 표준 호출 목록이있는 300 페이지 미만입니다. My O'Reilly Regex 서적은 K & R C보다 거의 두 배입니다.
Michael Shopsin

22

C가 만들어 졌던 시절과 모든 문자열에 대해 4 바이트의 추가 공간 은 아무리 짧아도 낭비 였습니다 !

또 다른 문제가 있습니다. C는 객체 지향적이 아니므로 모든 문자열의 길이를 접두사로 사용하는 경우에는 코드가 아닌 컴파일러 내장 형식으로 정의해야합니다 char*. 특수한 유형 인 경우 문자열을 상수 문자열과 비교할 수 없습니다.

String x = "hello";
if (strcmp(x, "hello") == 0) 
  exit;

정적 문자열을 문자열로 변환하거나 길이 접두사를 고려하기 위해 다른 문자열 함수를 가지려면 특별한 컴파일러 세부 정보가 있어야합니다.

나는 궁극적으로 Pascal과 달리 길이 접두사 방식을 선택하지 않았다고 생각합니다.


10
바운드 확인에도 시간이 걸립니다. 오늘날의 용어로는 사소한 것이지만 사람들이 약 4 바이트를 돌볼 때주의를 기울였습니다.
Steven Burnap

18
@StevenBurnap : 200MB 이미지의 모든 픽셀을 통과하는 내부 루프에 있다면 오늘날에도 그다지 쉽지 않습니다. 일반적으로 C를 작성하는 경우 빨리 가고 싶고 for루프가 이미 경계를 존중하도록 설정된 모든 반복에서 쓸모없는 바운드 검사에 시간을 낭비하고 싶지 않습니다 .
Matteo Italia

4
@ VF1 "오늘 다시"2 바이트 일 수 있습니다 (DEC PDP / 11 누구입니까?)
ClickRick

7
그것은 단지 "하루로 돌아가는"것이 아닙니다. C가 대상으로하는 소프트웨어의 경우 OS 커널, 장치 드라이버, 임베디드 실시간 소프트웨어 등과 같은 "휴대용 어셈블리 언어"로 지정됩니다. 경계 검사에 대한 6 가지 지침을 낭비하는 것은 중요하며, 대부분의 경우 "범위를 벗어난"상태 여야합니다 (다른 프로그램 저장소에 무작위로 액세스 할 수없는 경우 디버거를 작성하는 방법은 무엇입니까?).
James Anderson

3
BCPL의 주장이 길어 졌다는 점을 고려하면 이것은 실제로 다소 약한 주장입니다. Pascal이 1 워드로 제한 되었기 때문에 일반적으로 8 또는 9 비트 만 비트 제한이었습니다 (문자열의 일부를 공유 할 가능성도 배제합니다. 그리고 길이 다음에 배열이 오는 구조체로 문자열을 선언하면 실제로 특별한 컴파일러 지원이 필요하지 않습니다.
Voo

11

C에서, 어레이의 연속 서브 세트는 또한 어레이이며 그와 같이 작동 될 수있다. 이는 읽기 및 쓰기 작업 모두에 적용됩니다. 크기가 명시 적으로 저장된 경우이 특성이 유지되지 않습니다.


6
"디자인이 다를 것"은 디자인이 다른 이유가 아닙니다.
VF1

7
@ VF1 : 표준 파스칼로 프로그래밍 한 적이 있습니까? C는 어레이와 합리적으로 융통성을 발휘할 수있는 능력은 어셈블리에 비해 크게 개선되었으며 (안전이 전혀 없음) 1 세대 유형 안전 언어 (정확한 어레이 경계를 포함한 과잉 유형 안전)
MSalters

5
배열을 슬라이스하는이 기능은 실제로 C89 디자인에 대한 거대한 논쟁입니다.

올드 스쿨 포트란 해커도이 속성을 잘 사용합니다 (하지만 슬라이스를 포트란의 배열로 전달해야합니다). 혼란스럽고 프로그래밍이나 디버깅이 힘들지만 작업 할 때는 빠르고 우아합니다.
dmckee

3
슬라이싱을 허용하는 흥미로운 디자인 대안이 하나 있습니다. 배열과 함께 길이를 저장하지 마십시오. 배열에 대한 포인터의 경우 포인터와 함께 길이를 저장하십시오. 실제 C 배열 만 있으면 크기는 컴파일 시간 상수이며 컴파일러에서 사용할 수 있습니다. 더 많은 공간이 필요하지만 길이를 유지하면서 슬라이스 할 수 있습니다. &[T]예를 들어 Rust는 유형에 대해이 작업을 수행합니다 .

8

길이가 태그로 지정된 배열의 가장 큰 문제는 길이를 저장하는 데 필요한 공간이 아니거나 저장 방법에 대한 질문이 아닙니다 (짧은 배열에 1 바이트를 추가하면 일반적으로 불쾌하지 않으며 4를 사용하지 않습니다) 긴 배열의 경우 추가 바이트이지만 짧은 배열의 경우에도 4 바이트를 사용하는 것이 좋습니다). 훨씬 더 큰 문제는 다음과 같은 주어진 코드입니다.

void ClearTwoElements(int *ptr)
{
  ptr[-2] = 0;
  ptr[2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo+2);
  ClearTwoElements(foo+7);
  ClearTwoElements(foo+1);
  ClearTwoElements(foo+8);
}

코드가 첫 번째 호출을 수락 할 수 ClearTwoElements있지만 두 번째 호출을 거부 할 수있는 유일한 방법 은 ClearTwoElements메소드가 각 파트가 foo어떤 파트를 아는 것 외에도 어레이의 파트에 대한 참조를 수신하고 있음을 알기에 충분한 정보를 수신하는 것 입니다. 일반적으로 포인터 매개 변수 전달 비용이 두 배가됩니다. 또한 각 배열 앞에 끝을지나 주소에 대한 포인터 (유효성 검증을위한 가장 효율적인 형식)가 오는 경우 최적화 된 코드는 ClearTwoElements다음과 같습니다.

void ClearTwoElements(int *ptr)
{
  int* array_end = ARRAY_END(ptr);
  if ((array_end - ARRAY_BASE(ptr)) < 10 ||
      (ARRAY_BASE(ptr)+4) <= ADDRESS(ptr) ||          
      (array_end - 4) < ADDRESS(ptr)))
    trap();
  *(ADDRESS(ptr) - 4) = 0;
  *(ADDRESS(ptr) + 4) = 0;
}

일반적으로 메소드 호출자는 배열의 시작 부분에 대한 포인터 또는 메소드에 대한 마지막 요소를 합법적으로 완벽하게 전달할 수 있습니다. 메소드가 전달 된 배열 외부에있는 요소에 액세스하려고 시도하는 경우에만 그러한 포인터로 인해 문제가 발생합니다. 결과적으로, 호출 된 메소드는 먼저 인수의 유효성을 검증하기위한 포인터 산술이 범위를 벗어나지 않을 정도로 배열이 충분히 큰지 확인한 다음 포인터 계산을 수행하여 인수의 유효성을 검증해야합니다. 그러한 검증에 소요되는 시간은 실제 작업에 소요되는 비용을 초과 할 가능성이 있습니다. 또한 메소드를 작성하여 호출하면 더 효율적일 수 있습니다.

void ClearTwoElements(int arr[], int index)
{
  arr[index-2] = 0;
  arr[index+2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo,2);
  ClearTwoElements(foo,7);
  ClearTwoElements(foo,1);
  ClearTwoElements(foo,8);
}

물체를 식별하기 위해 무언가를 식별하고 무언가를 식별하기 위해 무언가를 결합하는 유형의 개념은 좋은 것입니다. 그러나 유효성 검사를 수행 할 필요가 없으면 C 스타일 포인터가 더 빠릅니다.


배열의 런타임 크기가있는 경우 배열에 대한 포인터는 배열 요소에 대한 포인터와 근본적으로 다릅니다. 후자는 전혀 새로운 배열을 만들지 않고 이전으로 직접 변환 할 수 없습니다. []포인터의 경우 구문이 여전히 존재할 수 있지만 이러한 가상의 "실제"배열과는 다를 수 있으며 설명하는 문제는 없을 것입니다.
hyde

@hyde : 문제는 객체 기본 주소를 알 수없는 포인터에 대해 산술을 허용해야하는지 여부입니다. 또한 구조 내 배열이라는 또 다른 어려움을 잊었습니다. 그것에 대해 생각하면, 각 포인터가 포인터 자체의 주소뿐만 아니라 상하 법을 포함하지 않아도 구조 내에 저장된 배열을 가리킬 수있는 포인터 유형이 있는지 확실하지 않습니다. 액세스 할 수있는 범위.
supercat

interseting point. 그래도 이것이 여전히 amon의 대답으로 줄어 듭니다.
VF1

질문은 배열에 대해 묻습니다. 포인터는 메모리 주소이며 의도를 이해하는 한 질문의 전제로 변경되지 않습니다. 배열은 길이가 길어지고 포인터는 변경되지 않습니다 (배열에 대한 포인터는 구조체에 대한 포인터와 매우 흡사하고 새롭고 독창적 인 유형이어야합니다).
hyde

@hyde : 언어의 의미를 충분히 변경 한 경우, 구조 내에 저장된 배열에는 약간의 어려움이 있지만 배열에 관련 길이가 포함될 수 있습니다. 의미론을 그대로 사용하면 배열 경계 검사는 배열 요소에 대한 포인터에 동일한 검사가 적용되는 경우에만 유용합니다.
supercat

7

C와 대부분의 다른 3 세대 언어와 내가 아는 가장 최근의 언어 사이의 근본적인 차이점 중 하나는 C가 프로그래머의 삶을 더 쉽고 안전하게 만들도록 설계되지 않았다는 것입니다. 프로그래머는 자신이 무엇을하고 있는지 알고 정확히 그 일만하고 싶었다는 기대로 설계되었습니다. '장면 뒤'에서는 아무것도하지 않으므로 놀라지 않아도됩니다. 컴파일러 수준 최적화조차도 선택 사항입니다 (Microsoft 컴파일러를 사용하지 않는 한).

프로그래머가 코드에서 범위 검사를 작성하려는 경우 C는이를 수행하기에 간단하지만 공간, 복잡성 및 성능 측면에서 해당 가격을 지불하도록 선택해야합니다. 몇 년 동안 화를 내지는 않았지만, 제약 기반 의사 결정의 개념을 극복하기 위해 프로그래밍을 가르 칠 때 여전히 사용합니다. 기본적으로, 원하는 것을 원하는대로 선택할 수 있지만, 모든 결정에는주의해야 할 가격이 있습니다. 다른 사람들에게 자신의 프로그램이 무엇을 원하는지 말하기 시작할 때 이것은 더욱 중요합니다.


3
C는 진화 한만큼 "디자인 된"것이 아니었다. 원래와 같은 선언 은 5 개 항목 배열로 int f[5];생성되지 않습니다 f. 대신에와 동일합니다 int CANT_ACCESS_BY_NAME[5]; int *f = CANT_ACCESS_BY_NAME;. 이전 선언은 컴파일러가 실제로 배열 시간을 "이해"할 필요없이 처리 될 수 있습니다. 공간을 할당하기 위해 어셈블러 지시문을 출력 f해야했고 배열과 관련이 있다는 것을 잊을 수있었습니다 . 배열 유형의 일관되지 않은 동작은 이것에서 비롯됩니다.
supercat

1
프로그래머가 C가 요구하는 정도까지 무엇을하고 있는지 알 수 없음이 밝혀졌습니다.
코드 InChaos

7

짧은 답변:

C는 저수준 프로그래밍 언어이므로 이러한 문제를 직접 처리해야하지만이 를 구현 하는 방식에 더 큰 유연성이 추가 됩니다.

C는 배열로 컴파일 타임 개념을 가지고 있지만 길이는 초기화되지만 모든 것은 단순히 데이터 시작에 대한 단일 포인터로 저장됩니다. 배열 길이를 배열과 함께 함수에 전달하려면 직접 수행하십시오.

retval = my_func(my_array, my_array_length);

또는 포인터와 길이 또는 다른 솔루션으로 구조체를 사용할 수 있습니다.

고급 언어는 배열 유형의 일부로이를 수행합니다. C에서는이 작업을 직접 수행해야 할 책임뿐만 아니라 수행 방법을 선택할 수있는 유연성도 제공됩니다. 그리고 작성 하는 모든 코드가 이미 배열의 길이를 알고 있다면 길이를 변수로 전달할 필요가 없습니다.

명백한 단점은 포인터로 전달 된 배열에 대한 고유 한 경계 검사가 없으면 위험한 코드를 만들 수 있지만 이는 저수준 / 시스템 언어의 특성과 그들이주는 절충입니다.


1
+1 "작성하는 모든 코드가 이미 배열의 길이를 알고 있다면 길이를 변수로 전달할 필요가 없습니다."
林果 皞

pointer + length 구조체 만 언어와 표준 라이브러리에 구워 졌다면. 너무 많은 보안 허점을 피할 수있었습니다.
코드 InChaos

그렇다면 실제로는 C가 아닙니다. 그렇게하는 다른 언어가 있습니다. C는 당신을 낮은 수준으로 만듭니다.
thomasrutter

C는 저수준 프로그래밍 언어로 개발되었으며 많은 방언은 여전히 ​​저수준 프로그래밍을 지원하지만 많은 컴파일러 작성자는 실제로 저수준 언어라고 할 수없는 방언을 선호합니다. 그것들은 저수준 문법을 허용하고 심지어 요구하지만, 문법에 의해 내포 된 시맨틱과 행동이 일치하지 않는 고급 구조를 추론하려고 시도합니다.
supercat 2019

5

추가 저장 장치의 문제는 문제이지만 내 생각에는 사소한 문제입니다. 결국, 대부분의 시간은 어쨌든 길이를 추적해야하지만 amon은 종종 정적으로 추적 할 수 있다고 지적했습니다.

더 큰 문제는 길이를 저장할 위치 와 길이입니다. 모든 상황에서 작동하는 장소는 없습니다. 데이터 직전에 메모리에 길이를 저장한다고 말할 수 있습니다. 배열이 메모리를 가리 키지 않지만 UART 버퍼와 같은 것이면 어떻습니까?

길이를 줄이면 프로그래머는 적절한 상황에 대한 자체 추상화를 만들 수 있으며 범용 사례에 사용할 수있는 준비된 라이브러리가 많이 있습니다. 진짜 질문은 왜 이러한 추상화가 보안에 민감한 응용 프로그램에서 사용 되지 않습니까?


1
You might say just store the length in the memory just before the data. What if the array isn't pointing to memory, but something like a UART buffer?좀 더 설명해 주시겠습니까? 또한 너무 자주 발생하거나 드문 경우일까요?
Mahdi

내가 디자인했다면 함수 인수는 함수 T[]와 같지 T*않고 포인터와 크기의 튜플을 함수에 전달합니다. 고정 크기 배열은 C에서와 같이 포인터로 붕괴하는 대신 이러한 배열 슬라이스로 붕괴 될 수 있습니다.이 방법의 주요 장점은 그 자체로는 안전하지 않다는 것이 아니라 표준 라이브러리를 포함한 모든 것이 가능한 규칙입니다. 짓다.
코드 InChaos

1

에서 C 언어의 개발 :

구조는 기계의 메모리에 직관적 인 방식으로 매핑해야하지만 배열이 포함 된 구조에는 배열의 기본을 포함하는 포인터를 숨길 수있는 좋은 장소가 없었으며 배열을 정렬하는 편리한 방법도 없었습니다. 초기화되었습니다. 예를 들어, 초기 Unix 시스템의 디렉토리 항목은 C에서 다음과 같이 설명 될 수 있습니다.
struct {
    int inumber;
    char    name[14];
};
나는 구조가 추상 객체를 특성화하는 것뿐만 아니라 디렉토리에서 읽을 수있는 비트 모음을 설명하기를 원했습니다. 컴파일러 name는 시맨틱 스가 요구 한 포인터를 어디에 숨길 수 있습니까 ? 구조가 더 추상적으로 생각되고 포인터 공간이 어떻게 든 숨겨져도 복잡한 객체를 할당 할 때 이러한 포인터를 올바르게 초기화하는 기술적 문제를 처리하려면 어떻게해야합니까? 아마도 구조를 포함하는 배열을 포함하는 구조를 임의의 깊이로 지정했을 수 있습니다.

이 솔루션은 유형이없는 BCPL과 유형 C 사이의 진화 체인에서 결정적인 점프를 구성했습니다. 저장에서 포인터의 구체화를 제거하고 대신 배열 이름이 표현식에 언급 될 때 포인터를 작성했습니다. 오늘날의 C에서 유지되는 규칙은 배열 유형의 값이 표현식에 나타날 때 배열을 구성하는 첫 번째 객체에 대한 포인터로 변환된다는 것입니다.

이 구절은 대부분의 상황에서 배열 표현식이 포인터로 붕괴되는 이유를 설명하지만 배열 길이가 배열 자체와 함께 저장되지 않은 이유에도 동일한 이유가 적용됩니다. Ritchie처럼 메모리에서 형식 정의와 해당 표현 사이의 일대일 매핑을 원한다면 해당 메타 데이터를 저장하기에 적합한 곳이 없습니다.

또한 다차원 배열에 대해 생각하십시오. 각 차원의 길이 메타 데이터를 어디에 저장하여 다음과 같은 방법으로 배열을 계속 걸을 수 있습니까?

T *p = &a[0][0];

for ( size_t i = 0; i < rows; i++ )
  for ( size_t j = 0; j < cols; j++ )
    do_something_with( *p++ );

-2

이 질문은 C에 배열이 있다고 가정합니다. 배열이라고하는 것은 연속적인 데이터 시퀀스 및 포인터 산술 연산을위한 구문 설탕 일뿐입니다.

다음 코드는 실제로 문자열인지 모르면서 int 크기의 청크에서 src에서 dst로 일부 데이터를 복사합니다.

char src[] = "Hello, world";
char dst[1024];
int *my_array = src; /* What? Compiler warning, but the code is valid. */
int *other_array = dst;
int i;
for (i = 0; i <= sizeof(src)/sizeof(int); i++)
    other_array[i] = my_array[i]; /* Oh well, we've copied some extra bytes */
printf("%s\n", dst);

C가 그렇게 단순화 된 이유는 무엇입니까? 이 새로운 질문에 대한 정답을 모르겠습니다. 그러나 일부 사람들은 종종 C가 더 읽기 쉽고 이식 가능한 어셈블러라고 말합니다.


2
나는 당신이 그 질문에 대답하지 않았다고 생각합니다.
Robert Harvey

2
당신이 말한 것은 사실이지만, 묻는 사람은 이것이 왜 그런지 알고 싶어합니다 .

9
C의 별명 중 하나는 "휴대용 어셈블리"입니다. 최신 버전의 표준이 핵심 개념에 더 높은 수준의 개념을 추가 한 반면, 핵심 개념은 단순하지 않은 간단한 구성 및 명령으로 구성되어 있습니다. 이는 대부분의 디자인 결정이 언어로 이루어집니다. 런타임에 존재하는 유일한 변수는 정수, 부동 소수점 및 포인터입니다. 명령어에는 산술, 비교 및 ​​점프가 포함됩니다. 그 밖의 모든 것은 그 위에 얇은 층 빌드입니다.

8
C에 배열이 없다고 말하는 것은 잘못입니다. 다른 구문으로 동일한 이진 파일을 생성 할 수없는 방법을 고려할 때 (적어도 배열 크기를 결정하기 위해 #defines를 사용하는 경우는 아닙니다). C의 배열 "연속적인 데이터 시퀀스"이며 별다른 것이 아닙니다. 배열처럼 포인터를 사용하는 것은 배열 자체가 아닌 (명시적인 포인터 산술 대신) 구문상의 설탕입니다.
hyde

2
예, 다음 코드를 고려하십시오 struct Foo { int arr[10]; }. arr포인터가 아닌 배열입니다.
Steven Burnap
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.