[c] C 포인터 : 고정 된 크기의 배열을 가리킴

이 질문은 C 전문가에게 나옵니다.

C에서는 다음과 같이 포인터를 선언 할 수 있습니다.

char (* p)[10];

.. 기본적으로이 포인터는 10 개의 문자 배열을 가리 킵니다. 이와 같은 포인터 선언에 대한 깔끔한 점은 p에 다른 크기의 배열 포인터를 할당하려고하면 컴파일 시간 오류가 발생한다는 것입니다. 간단한 char 포인터의 값을 p에 할당하려고하면 컴파일 시간 오류가 발생합니다. 나는 이것을 gcc로 시도했고 ANSI, C89 및 C99에서 작동하는 것 같습니다.

이런 포인터를 선언하는 것이 매우 유용 할 것 같습니다. 특히 포인터를 함수에 전달할 때 그렇습니다. 일반적으로 사람들은 다음과 같은 함수의 프로토 타입을 작성합니다.

void foo(char * p, int plen);

특정 크기의 버퍼를 예상했다면 plen 값을 테스트하기 만하면됩니다. 그러나 p를 전달하는 사람이 실제로 해당 버퍼에있는 충분한 메모리 위치를 제공한다는 보장은 없습니다. 이 함수를 호출 한 사람이 옳은 일을하고 있다는 것을 믿어야합니다. 반면에 :

void foo(char (*p)[10]);

.. 호출자가 지정된 크기의 버퍼를 제공하도록 강제합니다.

이것은 매우 유용 해 보이지만 내가 본 적이있는 코드에서 이와 같이 선언 된 포인터를 본 적이 없습니다.

내 질문은 : 사람들이 이와 같은 포인터를 선언하지 않는 이유가 있습니까? 명백한 함정이 보이지 않습니까?



답변

귀하의 게시물에서 말하는 것은 절대적으로 정확합니다. 나는 모든 C 개발자가 C 언어에 대한 특정 수준의 숙련도에 도달하면 정확히 똑같은 발견과 똑같은 결론에 도달한다고 말하고 싶습니다.

응용 프로그램 영역의 세부 사항이 특정 고정 크기의 배열을 호출 할 때 (배열 크기는 컴파일-시간 상수 임) 이러한 배열을 함수에 전달하는 유일한 적절한 방법은 포인터-배열 매개 변수를 사용하는 것입니다.

void foo(char (*p)[10]);

(C ++ 언어에서 이것은 참조로도 수행됩니다.

void foo(char (&p)[10]);

).

이렇게하면 언어 수준 유형 검사가 활성화되어 정확하게 정확한 크기의 배열이 인수로 제공되는지 확인할 수 있습니다. 사실, 많은 경우 사람들은이 기술을 깨닫지도 못한 채 암시 적으로 사용하여 typedef 이름 뒤에 배열 유형을 숨 깁니다.

typedef int Vector3d[3];

void transform(Vector3d *vector);
/* equivalent to `void transform(int (*vector)[3])` */
...
Vector3d vec;
...
transform(&vec);

또한 위 코드는 다음과 관련하여 변하지 않습니다. Vector3d 유형이 배열 또는 struct. Vector3d언제든지 의 정의를 배열에서 a struct로 전환 할 수 있으며 함수 선언을 변경할 필요가 없습니다. 두 경우 모두 함수는 “참조에 의해”집계 객체를 수신합니다 (이에 대한 예외가 있지만이 논의의 맥락에서 이것은 사실입니다).

그러나이 배열 전달 방법이 명시 적으로 너무 자주 사용되는 것을 보지 못할 것입니다. 왜냐하면 너무 많은 사람들이 다소 복잡한 구문으로 인해 혼란스러워하고 C 언어의 이러한 기능을 적절하게 사용하기에 충분하지 않기 때문입니다. 이러한 이유로 실제 생활에서 배열을 첫 번째 요소에 대한 포인터로 전달하는 것이 더 널리 사용되는 접근 방식입니다. “단순”해 보입니다.

그러나 실제로 배열 전달을 위해 첫 번째 요소에 대한 포인터를 사용하는 것은 매우 틈새 기술이며 매우 특정한 목적을 제공하는 트릭입니다. 유일한 목적은 다른 크기 (즉, 런타임 크기)의 배열 전달을 용이하게하는 것입니다. . 런타임 크기의 배열을 처리 할 수 ​​있어야하는 경우 이러한 배열을 전달하는 적절한 방법은 추가 매개 변수가 제공하는 구체적인 크기를 사용하여 첫 번째 요소에 대한 포인터를 사용하는 것입니다.

void foo(char p[], unsigned plen);

실제로 많은 경우 런타임 크기의 배열을 처리 할 수있는 것이 매우 유용하며 이는 메서드의 인기에 기여합니다. 많은 C 개발자는 고정 크기 배열을 처리해야하는 필요성을 결코 발견하지 못하거나 인식하지 못하므로 적절한 고정 크기 기술을 알지 못합니다.

그럼에도 불구하고 배열 크기가 고정되어 있으면 요소에 대한 포인터로 전달

void foo(char p[])

안타깝게도 요즘 널리 퍼져있는 주요 기술 수준의 오류입니다. 포인터-배열 기술은 이러한 경우 훨씬 더 나은 접근 방식입니다.

고정 크기 배열 전달 기술의 채택을 방해 할 수있는 또 다른 이유는 동적으로 할당 된 배열을 입력하는 순진한 접근 방식이 우세하기 때문입니다. 예를 들어, 프로그램이 유형의 고정 배열을 호출하는 경우 char[10](예제에서와 같이) 평균 개발자는 다음 malloc과 같은 배열을 사용합니다.

char *p = malloc(10 * sizeof *p);

이 배열은 다음과 같이 선언 된 함수에 전달할 수 없습니다.

void foo(char (*p)[10]);

이는 일반 개발자를 혼란스럽게하고 더 이상 생각하지 않고 고정 크기 매개 변수 선언을 포기하게 만듭니다. 하지만 실제로 문제의 근원은 순진한 malloc접근 방식에 있습니다. malloc위 형식은 런타임 크기의 배열에 예약해야합니다. 배열 유형에 컴파일 시간 크기가있는 경우 더 나은 방법 malloc은 다음과 같습니다.

char (*p)[10] = malloc(sizeof *p);

물론 이것은 위에서 선언 한 것에 쉽게 전달할 수 있습니다. foo

foo(p);

컴파일러는 적절한 유형 검사를 수행합니다. 그러나 다시 말하지만 이것은 준비되지 않은 C 개발자에게는 지나치게 혼란 스럽기 때문에 “일반적인”평균 일상 코드에서 너무 자주 보지 않을 것입니다.


답변

나는 AndreyT의 답변에 추가하고 싶습니다 (누군가이 주제에 대한 자세한 정보를 찾고이 페이지를 우연히 발견하는 경우) :

이 선언에 대해 더 많이 다루기 시작하면서 C (분명히 C ++가 아님)에서 그들과 관련된 주요 핸디캡이 있음을 깨달았습니다. 호출자에게 작성한 버퍼에 대한 const 포인터를 제공하려는 상황이있는 것은 매우 일반적입니다. 불행히도 C에서 이와 같은 포인터를 선언 할 때는 불가능합니다. 즉, C 표준 (6.7.3-단락 8)은 다음과 같이 상충됩니다.


   int array[9];

   const int (* p2)[9] = &array;  /* Not legal unless array is const as well */

이 제약 조건은 C ++에 존재하지 않는 것처럼 보이므로 이러한 유형의 선언이 훨씬 더 유용합니다. 그러나 C의 경우 고정 된 크기의 버퍼에 대한 const 포인터를 원할 때마다 일반 포인터 선언으로 돌아 가야합니다 (버퍼 자체가 처음으로 const로 선언되지 않은 경우). 이 메일 스레드에서 더 많은 정보를 찾을 수 있습니다 : 링크 텍스트

이것은 내 의견으로는 심각한 제약이며 사람들이 일반적으로 C에서 이와 같은 포인터를 선언하지 않는 주된 이유 중 하나 일 수 있습니다. 다른 하나는 대부분의 사람들이 이와 같은 포인터를 다음과 같이 선언 할 수 있다는 사실조차 모르고 있다는 사실입니다. AndreyT가 지적했습니다.


답변

명백한 이유는이 코드가 컴파일되지 않기 때문입니다.

extern void foo(char (*p)[10]);
void bar() {
  char p[10];
  foo(p);
}

배열의 기본 승격은 규정되지 않은 포인터입니다.

또한 볼 이 질문을 사용하여 foo(&p)작동합니다.


답변

또한이 구문을 사용하여 더 많은 유형 검사를 사용하고 싶습니다.

그러나 포인터를 사용하는 구문과 정신적 모델이 더 간단하고 기억하기 쉽다는데도 동의합니다.

여기에 내가 만난 몇 가지 장애물이 있습니다.

  • 어레이에 액세스하려면 (*p)[]다음을 사용해야합니다 .

    void foo(char (*p)[10])
    {
        char c = (*p)[3];
        (*p)[0] = 1;
    }

    대신 char에 대한 로컬 포인터를 사용하고 싶습니다.

    void foo(char (*p)[10])
    {
        char *cp = (char *)p;
        char c = cp[3];
        cp[0] = 1;
    }

    그러나 이것은 올바른 유형을 사용하는 목적을 부분적으로 무효화합니다.

  • 배열의 주소를 배열에 대한 포인터에 할당 할 때 주소 연산자를 사용하는 것을 기억해야합니다.

    char a[10];
    char (*p)[10] = &a;

    address-of 연산자는 &a에 할당 할 올바른 유형으로 에서 전체 배열의 주소를 가져옵니다 p. 연산자 a가 없으면 자동으로 배열의 첫 번째 요소 주소로 변환됩니다.&a[0] 유형이 다른 .

    이 자동 변환이 이미 일어나고 있기 때문에 나는 항상 &필요하다고 생각합니다. &다른 유형의 변수에 대한 사용과 일치 하지만 배열이 특별하고& 주소 값이 동일하더라도 올바른 유형의 주소를 얻으려면이 .

    내 문제의 한 가지 이유는 80 년대에 K & R C를 배웠기 때문에 &아직 전체 배열 에서 연산자를 사용할 수 없었습니다 (일부 컴파일러는이를 무시하거나 구문을 허용했지만). 그건 그렇고, 배열 포인터가 채택되기 어려운 또 다른 이유 일 수 있습니다. ANSI C 이후로만 제대로 작동하고 &연산자 제한이 너무 어색하다고 생각하는 또 다른 이유 일 수 있습니다.

  • 하면 typedef되는 하지 (공통 헤더 파일) 포인터 – 어레이의 유형을 만드는 데 사용, 다음 글로벌 포인터 – 어레이는 더 복잡 필요 extern파일을 통해 공유하는 선언을 :

    fileA:
    char (*p)[10];
    
    fileB:
    extern char (*p)[10];

답변

간단히 말해서 C는 그런 식으로하지 않습니다. 유형의 배열은 T첫 번째에 대한 포인터로 전달됩니다.T 에 그게 전부입니다.

이를 통해 다음과 같은 표현식을 사용하여 배열을 반복하는 것과 같은 멋지고 우아한 알고리즘을 사용할 수 있습니다.

*dst++ = *src++

단점은 크기 관리가 귀하에게 달려 있다는 것입니다. 안타깝게도이 작업을 성실하게 수행하지 않으면 C 코딩에서 수백만 개의 버그가 발생하고 악의적 인 악용이 발생할 수 있습니다.

C에서 요구하는 것에 가까운 것은 struct (값으로) 또는 포인터로 (참조로) 전달하는 것입니다. 이 작업의 양쪽에 동일한 구조체 유형이 사용되는 한, 참조를 전달하는 코드와이를 사용하는 코드는 처리되는 데이터의 크기에 대해 일치합니다.

구조체는 원하는 데이터를 포함 할 수 있습니다. 잘 정의 된 크기의 배열을 포함 할 수 있습니다.

그럼에도 불구하고 당신이나 무능하거나 악의적 인 코더가 캐스트를 사용하여 컴파일러를 속여서 구조체를 다른 크기로 취급하는 것을 막는 것은 없습니다. 이런 종류의 일을 할 수있는 거의 풀리지 않는 능력은 C 디자인의 일부입니다.


답변

여러 가지 방법으로 문자 배열을 선언 할 수 있습니다.

char p[10];
char* p = (char*)malloc(10 * sizeof(char));

값으로 배열을받는 함수의 프로토 타입은 다음과 같습니다.

void foo(char* p); //cannot modify p

또는 참조 :

void foo(char** p); //can modify p, derefernce by *p[0] = 'f';

또는 배열 구문으로 :

void foo(char p[]); //same as char*


답변

이 솔루션을 권장하지 않습니다.

typedef int Vector3d[3];

Vector3D에 알아야 할 유형이 있다는 사실을 모호하게하기 때문입니다. 프로그래머는 일반적으로 동일한 유형의 변수가 다른 크기를 가질 것이라고 기대하지 않습니다. 고려 :

void foo(Vector3d a) {
   Vector3D b;
}

여기서 sizeof a! = sizeof b