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


119

이 질문은 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]);

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

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

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


3
참고 : C99 이후 배열은 제목에서 제안한대로 고정 크기 일 필요 10가 없으며 범위의 모든 변수로 대체 할 수 있습니다
MM

답변:


173

귀하의 게시물에서 말하는 것은 절대적으로 정확합니다. 나는 모든 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 개발자에게는 지나치게 혼란 스럽기 때문에 "일반적인"평균 일상 코드에서 너무 자주 보지 않을 것입니다.


2
대답은 sizeof ()가 어떻게 성공하는지, 얼마나 자주 실패하는지, 그리고 항상 실패하는 방법에 대한 매우 간결하고 유익한 설명을 제공합니다. 대부분의 C / C ++ 엔지니어에 대한 당신의 관찰은 이해하지 못하기 때문에 그들이 이해한다고 생각하는 일을하는 것은 내가 한동안 본 것보다 더 예언적인 일 중 하나이며 베일은 그것이 설명하는 정확성에 비하면 아무것도 아닙니다. 진심입니다. 좋은 대답입니다.
WhozCraig 2012 년

난 그냥 브라보와 Q와 A. 모두 감사합니다,이 답변에 따라 일부 코드를 리팩토링
페리

1
const이 기술로 재산을 어떻게 처리하는지 알고 싶습니다 . const char (*p)[N]인수에 대한 포인터와 호환하지 않는 것 char table[N];대조적으로, 간단한 char*A의 호환성을 유지 PTR const char*인수입니다.
시안

4
그것은 당신의 배열의 요소에 액세스 할 점에 유의하는 것이 도움이 될 수도, 당신은 할 필요 (*p)[i]하지 *p[i]. 후자는 배열의 크기만큼 뛰어 오를 것인데, 이는 거의 확실히 원하는 것이 아닙니다. 적어도 나를 위해이 구문을 배우는 것은 실수를 막기보다는 오히려 일으켰습니다. float *를 전달하기 만하면 정확한 코드를 더 빨리 얻었을 것입니다.
Andrew Wagner

1
예 @mickey, 당신이 제안한 것은 const가변 요소의 배열에 대한 포인터입니다. 그리고 예, 이것은 불변 요소의 배열에 대한 포인터와는 완전히 다릅니다.
Cyan

11

나는 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가 지적했습니다.


2
이는 컴파일러 관련 문제인 것으로 보입니다. gcc 4.9.1을 사용하여 복제 할 수 있었지만 clang 3.4.2는 non-const에서 const 버전으로 문제없이 이동할 수있었습니다. 나는 C11 사양 (내 버전의 9 페이지 ... 호환되는 두 가지 인증 된 유형에 대해 이야기하는 부분)을 읽었으며 이러한 변환이 불법이라는 데 동의합니다. 그러나 실제로는 경고없이 항상 char *에서 char const *로 자동 변환 할 수 있다는 것을 알고 있습니다. IMO, clang은 gcc보다 이것을 허용하는 데 더 일관성이 있지만 사양이 이러한 자동 변환을 금지하는 것 같다는 데 동의합니다.
Doug Richardson

4

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

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

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

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


3
물론 foo (p)는 작동하지 않습니다. foo는 10 개 요소의 배열에 대한 포인터를 요청하므로 배열의 주소를 전달해야합니다 ...
Brian R. Bondy

9
그게 "분명한 이유"는 무엇입니까? 함수를 호출하는 적절한 방법은 foo(&p).
AnT

3
"명백하다"는 말이 잘못된 것 같아요. 나는 "가장 간단하다"는 것을 의미했습니다. 이 경우 p와 & p의 구분은 평균적인 C 프로그래머에게는 매우 모호합니다. 포스터가 제안한 것을하려는 사람은 내가 쓴 글을 쓰고 컴파일 타임 오류를 받고 포기할 것입니다.
Keith Randall

2

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

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

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

  • 어레이에 액세스하려면 (*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];

1

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

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

*dst++ = *src++

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

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

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

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


1

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

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*

2
고정 크기 배열은 char (*p)[10] = malloc(sizeof *p).
AnT

char array []와 char * ptr의 차이점에 대한 자세한 내용은 여기를 참조하십시오. stackoverflow.com/questions/1807530/…
t0mm13b

1

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

typedef int Vector3d[3];

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

void foo(Vector3d a) {
   Vector3D b;
}

여기서 sizeof a! = sizeof b


그는 이것을 해결책으로 제안하지 않았습니다. 그는 단순히 이것을 예로 사용했습니다.
figurassa

흠. 왜 sizeof(a)같지 sizeof(b)않습니까?
sherrellbc

0

어쩌면 내가 뭔가를 놓치고 있을지도 모르지만 ... 배열은 상수 포인터이기 때문에 기본적으로 포인터를 전달할 필요가 없음을 의미합니다.

그냥 사용할 수 void foo(char p[10], int plen);없습니까?


4
배열은 상수 포인터가 아닙니다. 어레이에 대한 FAQ를 읽어보십시오.
AnT

2
여기서 중요한 것은 (매개 변수로서의 1 차원 배열), 사실은 상수 포인터로 붕괴된다는 것입니다. 덜 현명 해지는 방법에 대한 FAQ를 읽어보세요.
fortran

-2

내 컴파일러 (vs2008)에서는 char (*p)[10]C 파일로 컴파일하더라도 괄호가없는 것처럼 문자 포인터 배열로 처리 됩니다. 이 "변수"에 대한 컴파일러 지원이 있습니까? 그렇다면 그것을 사용하지 않는 주된 이유입니다.


1
-1 틀 렸습니다. vs2008, vs2010, gcc에서 잘 작동합니다. 특히이 예제는 잘 작동합니다. stackoverflow.com/a/19208364/2333290
kotlomoy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.