source

C가 왜 "가짜 배열"을 가지고 있을까요?

ittop 2023. 10. 24. 21:34
반응형

C가 왜 "가짜 배열"을 가지고 있을까요?

유닉스 헤이터즈 핸드북을 읽고 있는데 9장에 이해가 잘 안 가는 부분이 있습니다.

C도 사실 어레이가 없습니다.배열처럼 보이지만 실제로는 메모리 위치를 가리키는 포인터입니다.

메모리 위치를 색인화하는 포인터를 사용하는 것 외에는 메모리에 배열을 저장할 방법이 없습니다.C가 어떻게 "가짜" 배열을 구현합니까?이 주장에 대한 진실성이 있습니까?

저자의 요점은 C 배열이 정말로 포인터 연산의 얇은 베니어판에 불과하다는 것이라고 생각합니다.첨자 연산자는 간단히 다음과 같이 정의됩니다.a[b] == *(a + b), 그래서 쉽게 말할 수 있습니다.5[a]대신에a[5]마지막 인덱스를 지나 배열에 접근하는 것과 같은 다른 끔찍한 일들을 합니다.

이와 비교하면, "진정한 배열"은 자신의 크기를 알고 있거나, 포인터 산술을 할 수 없고, 마지막 인덱스를 오류 없이 통과하거나, 다른 항목 유형을 사용하여 내용에 액세스할 수 없는 배열입니다.다시 말해, "진정한 배열"이란 표현을 하나의 표현으로 묶지 않고, 예를 들어 링크된 목록일 수도 있습니다.

추신. 수고를 덜기 위해:저는 이에 대한 의견이 별로 없고, 그냥 책에서 인용한 내용을 설명하는 거예요.

C 배열과 포인터 사이에는 차이가 있으며, 이는 의 출력으로 알 수 있습니다.sizeof()표현. 예를 들어:예를 들어,

void sample1(const char * ptr)
{
   /* s1 depends on pointer size of architecture */
   size_t s1 = sizeof(ptr); 
}
size_t sample2(const char arr[])
{
   /* s2 also depends on pointer size of architecture, because arr decays to pointer */
   size_t s2 = sizeof(arr); 
   return s2;
}
void sample3(void)
{
   const char arr[3];
   /* s3 = 3 * sizeof(char) = 3 */
   size_t s2 = sizeof(arr); 
}
void sample4(void)
{
   const char arr[3];
   /* s4 = output of sample2(arr) which... depends on pointer size of architecture, because arr decays to pointer */
   size_t s4 = sample2(arr); 
}

sample2그리고.sample4특히 사람들이 C 배열을 C 포인터와 혼동하는 경향이 있는 이유는 다른 언어에서는 단순히 배열을 함수에 인수로 전달하여 호출자 함수에서 했던 것과 '똑같이' 작동하게 할 수 있기 때문일 것입니다.마찬가지로 C가 작동하기 때문에 배열 대신 포인터를 전달할 수 있으며 이는 '유효'한 반면 배열과 포인터의 구별이 더 명확한 다른 언어에서는 그렇지 않습니다.

당신은 또한 볼 수 있었습니다.sizeof()(C 배열이 포인터로 붕괴되기 때문에) C의 패스 바이 값 시맨틱스의 결과로 출력됩니다.

또한 일부 컴파일러들은 다음과 같은 C 구문도 지원합니다.

void foo(const char arr[static 2])
{
   /* arr must be **at least** 2 elements in size, cannot pass NULL */
}

당신이 인용한 명세서는 사실 틀렸습니다.C의 배열은 포인터가 아닙니다.

배열을 포인터로 구현하는 아이디어는 B 언어와 BCPL 언어(C의 조상)에서 사용되었지만 C로의 전환에서는 살아남지 못했습니다.C의 초기에는 B 및 BCPL과의 "역호환성"이 다소 중요하게 여겨졌는데, 이것이 C 어레이가 B 및 BCPL 어레이의 동작을 밀접하게 모방하는 이유입니다(즉, C 어레이가 포인터에 쉽게 "붕괴"됨).그럼에도 불구하고 C 어레이는 "메모리 위치에 대한 포인터"가 아닙니다.

그 책의 인용구는 완전히 가짜입니다.이런 오해는 초보자들 사이에 널리 퍼져있습니다.그러나 그것이 어떻게 책에 들어갈 수 있었는지는 저로서는 알 수 없습니다.

작성자는 아마도 프로그래머의 관점에서 볼 때 어레이가 2등 시민처럼 느껴질 정도로 제약을 받고 있다는 것을 의미할 것입니다.예를 들어, 두 개의 함수, 하나는 OK이고 다른 하나는 그렇지 않습니다.

int finefunction() {
    int ret = 5;
    return ret;
}

int[] wtffunction() {
    int ret[1] = { 5 };
    return ret;
}

어레이를 구조로 감싸면 이 문제를 해결할 수 있지만, 어레이가 다른 유형과는 다르다는 점을 강조할 뿐입니다.

struct int1 {
    int a[1];
}

int[] finefunction2() {
    struct int1 ret = { { 5 } };
    return ret;
}

이것의 또 다른 효과는 런타임에 배열 크기를 가져올 수 없다는 것입니다.

int my_sizeof(int a[]) {
    int size = sizeof(a);
    return size;
}

int main() {
    int arr[5];
    // prints 20 4, not 20 20 as it would if arrays were 1st class things
    printf("%d %d\n", sizeof(arr), my_sizeof(arr)); 
}

저자가 말하는 또 다른 방법은 C(및 C++) 용어에서 "array"는 대부분의 다른 언어와는 다른 것을 의미합니다.


그렇다면, 당신의 제목 질문은 어떻게 "진정한 배열"을 메모리에 저장할 것인가 입니다.음, "진정한 배열"이란 단 하나의 종류는 없습니다.C에서 진정한 어레이를 원한다면 기본적으로 두 가지 옵션이 있습니다.

  1. calloc을 사용하여 버퍼를 할당하고 포인터와 항목 수를 여기에 저장합니다.

    struct intarrayref {
      size_t count;
      int *data;
    }
    

    이 구조는 기본적으로 배열을 참조한 것으로, 함수 등에 잘 전달할 수 있습니다.실제 데이터의 복사본을 만드는 것과 같이 이를 조작하기 위한 함수를 작성할 수 있습니다.

  2. 유연한 배열 부재를 사용하고 단일 calloc으로 전체 구조를 할당합니다.

    struct intarrayobject {
        size_t count;
        int data[];
    }
    

이 경우 두 메타데이터를 모두 할당합니다(count), 그리고 어레이 데이터를 한 번에 저장할 수 있는 공간도 있습니다. 하지만 가격은 이 구조를 더 이상 가치 있는 것으로 돌릴 수 없다는 것입니다. 왜냐하면 이 구조는 여분의 데이터를 남겨둘 것이기 때문입니다.함수 등에 이 구조물에 포인터를 전달해야 합니다.따라서 이를 "진정한 배열"로 볼 것인지, 아니면 약간 향상된 일반 C 배열로 볼 것인지에 대한 의견이 필요합니다.

책 전체와 마찬가지로, 그것은 트롤링의 사례인데, 구체적으로는 거의 사실이지만 잘못된 것을 언급하여 왜 그것이 잘못된 것인지에 대한 분노한 반응을 간청하는 것과 관련이 있습니다.C는 포인터-투-어레이 유형(및 다차원 어레이)의 작동 방식에서 알 수 있듯이 실제 어레이/어레이 유형을 가지고 있습니다.

언급URL : https://stackoverflow.com/questions/41652112/why-would-c-have-fake-arrays

반응형