포인터 명확성의 배열 / 배열에 대한 C 포인터


463

다음 선언의 차이점은 무엇입니까?

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

더 복잡한 선언을 이해하기위한 일반적인 규칙은 무엇입니까?


54
다음은 C에서 복잡한 선언을 읽는 것에 대한 훌륭한 기사입니다. unixwiz.net/techtips/reading-cdecl.html
jesper

@jesper 불행하게도, 이 기사에는 중요하고 까다로운 constvolatile한정자가 누락되었습니다.
사용자가 아닙니다

이 질문과 관련이없는 @ not-a-user. 귀하의 의견은 관련이 없습니다. 삼가 해주세요.
user64742

답변:


439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

세 번째는 첫 번째와 같습니다.

일반적인 규칙은 연산자 우선 순위 입니다. 함수 포인터가 그림에 들어 오면 훨씬 더 복잡해질 수 있습니다.


4
따라서 32 비트 시스템의 경우 : int * arr [8]; / * 각각의 포인터에 대해 8x4 바이트 할당 / int (* arr) [8]; / 4 바이트 할당, 포인터 만 * /
George

10
아니. int * arr [8] : 할당 된 8x4 바이트 , 각 포인터에 4 바이트. int (* arr) [8]은 4 바이트입니다.
Mehrdad Afshari

2
내가 쓴 것을 다시 읽었어야했다. 각 포인터에 대해 4를 의미했습니다. 도와 주셔서 감사합니다!
George

4
첫 번째가 마지막과 같은 이유는 항상 선언자 주위에 괄호를 감쌀 수 있기 때문입니다. P [N]은 배열 선언자입니다. P (....)는 함수 선언자이고 * P는 포인터 선언자입니다. 따라서 다음의 모든 것은 괄호가없는 것과 동일합니다 (함수의 "()"중 하나를 제외하고 : int (((* p))); void ((g (void))); int * (a [1]); void (* (p ()))
Johannes Schaub-litb

2
당신의 설명에서 잘했습니다. 연산자의 우선 순위 및 연관성에 대한 자세한 내용은 Brian Kernighan 및 Dennis Ritchie가 작성한 C 프로그래밍 언어 (ANSI C 2 판)의 53 페이지를 참조하십시오. 연산자는 ( ) [ ] 왼쪽에서 오른쪽으로 연결 되며 각 요소가 int를 가리키는 크기 8의 배열과 정수를 보유한 크기 8의 배열에 대한 포인터로 *읽히는 것보다 우선 순위가 높습니다.int* arr[8]int (*arr)[8]
Mushy

267

K & R에서 제안한대로 cdecl 프로그램을 사용하십시오 .

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

다른 방식으로도 작동합니다.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )

@ankii 대부분의 Linux 배포에는 패키지가 있어야합니다. 자신 만의 바이너리를 만들 수도 있습니다.
sigjuice

아아, 죄송합니다, 여기 macOS. 가능한 경우 웹 사이트도 괜찮습니다. ^^ 이것에 대해 알려 주셔서 감사합니다.
ankii

2
@ankii Homebrew (및 아마도 MacPorts?)에서 설치할 수 있습니다. 그것들이 당신의 취향에 맞지 않으면 cdecl.org의 오른쪽 상단에있는 Github 링크에서 직접 빌드하는 것은 사소한 것입니다 (방금 macOS Mojave에서 빌드했습니다). 그런 다음 cdecl 바이너리를 PATH에 복사하십시오. 이처럼 간단한 것에 루트를 포함시킬 필요가 없기 때문에 $ PATH / bin을 권장합니다.
sigjuice

readme에서의 설치에 관한 작은 단락을 읽지 않았습니다. 종속성을 처리하기위한 명령과 플래그 만 있습니다. brew를 사용하여 설치됩니다. :)
ankii

1
내가 이것을 처음 읽었을 때 나는 "나는 결코이 수준으로 내려 가지 않을 것"이라고 생각했다. 다음날 나는 그것을 다운로드했다.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

126

공식 명칭이 있는지는 모르겠지만 Right-Left Thingy (TM)라고합니다.

변수에서 시작한 다음 오른쪽, 왼쪽 및 오른쪽으로 이동하십시오.

int* arr1[8];

arr1 정수에 대한 8 개의 포인터 배열입니다.

int (*arr2)[8];

arr2 8 정수 배열의 포인터 (오른쪽 왼쪽 괄호 블록)입니다.

int *(arr3[8]);

arr3 정수에 대한 8 개의 포인터 배열입니다.

복잡한 선언에 도움이됩니다.


19
"나선 규칙"이라는 이름으로 언급 된 것을 들었습니다 . 여기 에서 찾을 수 있습니다 .
fouric

6
@InkBlend : 나선 규칙은 왼쪽 규칙 과 다릅니다 . 전자는 실패 같은 경우에 int *a[][10]후자는 성공하면서.
legends2k

1
내가 생각 @dogeen 그 용어는 : 비얀 스트로브 스트 룹 함께 할 수있는 뭔가했다
사진 제공 : Anirudh Ramanathan

1
InkBlend와 legends2k가 말했듯이, 이것은 더 복잡하고 모든 경우에 작동하지 않는 나선형 규칙이므로 사용할 이유가 없습니다.
kotlomoy

( ) [ ]오른쪽에서 왼쪽으로의 오른쪽에서 왼쪽으로 의 lef를 잊지 마십시오* &
Mushy

28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 

세 번째는 안됩니다 : a는 8 크기의 정수 배열에 대한 포인터 배열입니까? 각 정수 배열의 크기가 8입니다.
Rushil Paul

2
@Rushil : 아니오, 마지막 아래 첨자 ( [5])는 내부 치수를 나타냅니다. 이는 (*a[8])첫 번째 차원이므로 배열의 외부 표현입니다. 각 요소가 a 가리키는 크기는 5의 다른 정수 배열입니다.
zeboidlund

세번째 감사합니다. 배열에 대한 포인터 배열을 작성하는 방법을 찾고 있습니다.
Deqing

15

마지막 두 가지에 대한 대답은 C의 황금률에서 공제 될 수도 있습니다.

사용에 따른 선언.

int (*arr2)[8];

역 참조하면 어떻게됩니까 arr2? 8 개의 정수 배열을 얻습니다.

int *(arr3[8]);

요소를 가져 오면 어떻게됩니까 arr3? 정수에 대한 포인터를 얻습니다.

함수에 대한 포인터를 다룰 때도 도움이됩니다. sigjuice의 예를 들어 보려면 :

float *(*x)(void )

역 참조하면 어떻게됩니까 x? 인수없이 호출 할 수있는 함수를 얻습니다. 전화하면 어떻게 되나요? 에 대한 포인터를 반환합니다 float.

그러나 연산자 우선 순위는 항상 까다 롭습니다. 그러나 선언에 따라 사용되므로 괄호를 사용하는 것도 실제로 혼란 스러울 수 있습니다. 적어도 나에게는 직관적 arr2으로 int에 대한 8 개의 포인터 배열처럼 보이지만 실제로는 다른 방법입니다. 익숙해 지기만하면됩니다. 나에게 묻는다면 항상 이러한 선언에 의견을 추가 할 수있는 이유 :)

편집 : 예

그런데 정적 행렬이 있고 포인터 산술을 사용하여 행 포인터가 범위를 벗어 났는지 확인하는 함수에서 방금 넘어졌습니다. 예:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

산출:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

border 값은 절대 변경되지 않으므로 컴파일러는이를 최적화 할 수 있습니다. 이것은 처음에 사용하려는 것과 다릅니다.const int (*border)[3] : 변수가 존재하는 한 값을 변경하지 않는 3 개의 정수 배열에 대한 포인터로 border를 선언합니다. 그러나 해당 포인터는 언제든지 이러한 다른 배열을 가리킬 수 있습니다. 우리는 인수에 대해 이런 종류의 동작을 원합니다 (이 함수는 그 정수를 변경하지 않기 때문에). 사용에 따른 선언.

(ps :이 샘플을 자유롭게 개선하십시오!)



3

엄지 손가락의 규칙을 마우스 오른쪽 단항 연산자 (등 [], ()왼쪽 사람 이상 등) 테이크 기본 설정. 따라서 int *(*ptr)()[];포인터 배열을 int로 반환하는 함수를 가리키는 포인터가됩니다 (괄호를 벗어나면 가능한 한 빨리 올바른 연산자를 가져 오십시오)


그것은 사실이지만, 또한 비방입니다. 배열을 반환하는 함수를 가질 수 없습니다. 나는 노력이있어 : error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];와 GCC 8에서$ gcc -std=c11 -pedantic-errors test.c
Cacahuete 프리 토에게

1
컴파일러가 오류를 발생시키는 이유는 우선 순위 규칙 상태의 올바른 해석으로 함수가 배열을 리턴하는 것으로 해석하기 때문입니다. 이것은 선언으로 일관 적이 지 만 법적 선언 int *(*ptr)();은 나중에 p()[3](또는 (*p)()[3]) 와 같은 표현 을 사용할 수 있습니다.
Luis Colorado 19

좋아, 이해한다면 배열 자체가 아닌 배열의 첫 번째 요소에 대한 포인터를 반환하는 함수를 만드는 것에 대해 이야기하고 나중에 배열을 반환하는 것처럼 해당 함수를 사용합니까? 재미있는 생각. 시도해 볼게. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }이처럼 호출 foo(arr)[4];이 포함되어야한다 arr[2][4], 권리?
Cacahuete Frito

맞아요.하지만 당신도 옳았 고 선언은 비방했습니다. :)
루이스 콜로라도

2

나는 우리가 간단한 규칙을 사용할 수 있다고 생각합니다 ..

example int * (*ptr)()[];
start from ptr 

" ptr 는"오른쪽으로 가십시오. .. "" 에 대한 포인터입니다. 이제 왼쪽으로 "("나옵니다. 오른쪽 "배열에"정수의 왼쪽 ""


"ptr은 참조하는 이름입니다."오른쪽으로 이동합니다 ). 이제 왼쪽으로 *이동합니다. "포인터로"입니다. 오른쪽으로 이동합니다 ). 이제 왼쪽으로 이동합니다. (바로 이동 나올 ()... 바로 갈 "인수를받는 함수에"이렇게 []"및 반환의 배열"잘 갈 ;끝을, 그래서 왼쪽으로 가서 ... *"포인터로"이동 왼쪽 ... int"정수"
Cacahuete Frito


2

내가 그것을 해석하는 방법은 다음과 같습니다.

int *something[n];

우선 순위 참고 : 배열 첨자 연산자 ( [])는 역 참조 연산자 ( *) 보다 우선 순위가 높습니다 .

따라서 여기에 []before 를 적용 *하여 명령문을 다음과 동일하게 만듭니다.

int *(something[i]);

선언이 어떻게 이해되는지에 주목하십시오 : int nummeans num는 aint , int *ptr또는 int (*ptr)(value at ptr)는 a이며 int,에 ptr대한 포인터를 만듭니다 int.

이것을 읽을 수 있습니다. ((이것의 i 번째 인덱스 값)의 값은 정수입니다. 따라서 (뭔가의 i 번째 인덱스 값)은 (정수 포인터)로, 정수 포인터의 배열을 만듭니다.

두 번째는

int (*something)[n];

이 진술을 이해하려면 다음 사실에 익숙해야합니다.

배열의 포인터 표현에 대한 참고 사항 somethingElse[i]은 다음과 같습니다.*(somethingElse + i)

따라서로 대체 somethingElse하면 선언 당 정수 (*something)인을 얻습니다 *(*something + i). 그래서, (*something)우리에게 (배열을 가리키는) 무언가와 같은 배열을주었습니다 .


0

나는 두 번째 선언이 많은 사람들에게 혼란을 겪고 있다고 생각합니다. 이해하기 쉬운 방법이 있습니다.

정수 배열, 즉 int B[8] .

또한 B를 가리키는 변수 A를 가지겠습니다. 이제 A의 값은 B입니다. (*A) == B 입니다. 따라서 A는 정수 배열을 가리 킵니다. 귀하의 질문에서 arr은 A와 유사합니다.

마찬가지로에서에서 int* (*C) [8]C는 정수에 대한 포인터 배열에 대한 포인터입니다.


0
int *arr1[5]

이 선언에서 arr1정수에 대한 5 개의 포인터 배열입니다. 이유 : 대괄호가 *보다 우선 순위가 높습니다 (영리 해제 연산자). 그리고이 유형에서는 행 수가 고정되어 있지만 (여기서는 5) 열 수는 가변적입니다.

int (*arr2)[5]

이 선언에서 arr25 요소의 정수 배열에 대한 포인터입니다. 이유 : 여기에서 () 괄호는 []보다 우선합니다. 그리고이 유형에서 행 수는 가변적이지만 열 수는 고정되어 있습니다 (여기서는 5).


-7

포인터가 증가하면 정수에 대한 포인터에서 다음 정수로갑니다.

포인터 배열에서 포인터가 증가하면 다음 배열로 이동합니다.


" 포인터의 배열에서 포인터가 증가하면 다음 배열로 점프합니다 ."
alk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.