- voronoi diagram
- CodeForces
- boj23054
- BOJ 30028
- Delaunay triangulation
- BOJ 30029
- dx dy
- 2023 SW - IT Contest
- 느리게 갱신되는 세그먼트 트리
- BOJ 31226
- 누텔라트리(hard)
- solved.ac
- 2025acpc
- hhs2003
- 충남대학교 2023 SW - IT
- BOJ 30026
- 27173
- 알고리즘
- 컴퓨터융합학부
- boj 30788
- 세그먼트 트리
- fortune's algorithm
- 오일러투어트리
- 27114
- BOJ 30027
- 수열과 쿼리 43
- BOJ17139
- 백준
- K8s
- 2023 Engineering Pair
황현석 일지
C의 Array Pointer 에 대한 고찰 및 응용 그리고 연습 본문
2025년 06월 20일, 새벽 3시 16분, 금일 19시30분에 나는 컴퓨터 프로그래밍 3의 C언어에 대한 시험을 치루어야 한다.
C언어를 배우고, 궁금해해서, 파고 이해하고, 응용하다보면, 재미있다. 중요한 것은 복습과 까먹지 않는 것, 무의식속에 잘 고히 간직해 두는 것이라고 생각한다.
C의 Array Pointer에 대해 학습한 내용을 적고, 그와 관련된 어려운 연습 문제를 풀이 하는 시간을 가지며, 독자도 같이 이해하는 시간을 가졌으면 좋겠다.
Array Pointer를 가지고 연습하며, 추가적으로 함수형 포인터 사용에도, 간단하게 적용할 예정이다.
함수형 포인터 그자체를 선언하는 것은 이 문제로 충분히 연습할 수 있다. 하지만 그에 대한 내용은 다루지 않는다.
int main () {
int a[5];
int *p = a;
int b[4][5];
int (*c)[5] = b;
}
크기가 5인 int array 가 변수 a에 초기화 되었다. a를 포인터로 사용할 경우, int *p에 할당 가능하다.
"배열의 포인터로의 붕괴(array to pointer decay)는 배열 형식(array type)이 표현식에 나타날 때 배열의 첫 번째 원소를 가리키는 포인터로 변환되는 현상을 말한다."
a를 표현식에서 사용할 경우, 배열 붕괴(array decay)에 의해 int[5] 타입이, int (*) 타입으로 암시적 변환 되어 int *p 에 할당 된 것이다.
b도 또한 마찬가지이다. int [4][5] 타입은, decay 후, int (*)[5]로 암시적 형변환이 일어나게 되며, int (*c)[5]에 할당 될 수 있다.
Application 응용
흠… 그럼 크기가 5인 int array의 pointer를 10개 모아놓은 배열을 또 가르키는 포인터는 어떻게 선언하지?
int (*p)[5]; // int [5]를 가르키는 포인터
int (*p[10])[5]; // int [5]를 가르키는 포인터의 크기가 10인 배열
int (*(*p)[10])[5]; // int [5]를 가르키는 포인터의 크기가 10인 배열을 가르키는 포인터
다음과 같이 선언할 수 있다. 나는 개인적으로 가장 작은 원소부터 선언하고, p를 (p)로 치환하고 다차원 배열 선언을 하는편이다.
- 타입 해석이 직관적이고 실수를 방지 할 수 있다.
- 가장 작은 단위(원소)를 먼저 선언하면, 타입이 어떤식으로 재사용되고, 어떤식으로 매개변수로 들어갈 수 있는지, 단계별 추적이 쉽다.
- 괄호로 p를 감싸며, 점진적으로 확장하기 때문에, 우선순위 실수, 괄후 누락, 배열/포인터 혼동 등을 방지 할 수 있다.
- 이미 선언한 타입을 기반으로 더 복잡한 타입을 만들 때 기존 선언을 그대로 활용할 수 있습니다.
다음과 같은 부차적 효과를 누릴 수 있기 때문에, bottom-up 방식을 선언한다.
문제 1.
크기 8×7×5짜리 3차원 배열이 있다. 이 배열의 각 원소는크기 4×9짜리 float 배열을 가리키는 포인터 3개로 이루어진 배열이다.그리고 이 3차원 배열 전체를 가리키는 포인터를 선언하라.
가장 작은 원소 단위는 무엇일까? 4x9 float 배열이라는 것을 알 수 있다. 그에 대한 선언은
float p[4][9]; 로 나타낼 수 있다. 왼쪽의 p를 가르키는 포인터로 이루어진 크기가 3인 배열을 선언하면 되므로 아래와 같다.
float (*p[3])[4][9]; 8x7x5짜리 3차원 배열의 원소가 왼쪽 타입이므로 왼족 타입이 그만한 크기의 배열을 가지고 있으면 되는 것이다.
float (*(p[8][7][5])[3])[4][9]; 해당 타입의 포인터는 p를 (*p)로 치환하면 된다.
float (*((*p)[8][7][5])[3])[4][9]; 가 정답이 되게 된다.
문제 2.
배열 2x10로 구성된 int 배열을 가르키는 포인터가 있다. 해당 포인터를 원소로 하는 크기가 10인 배열을 포인터로 가지는 42x10x53x65 짜리 배열을 선언하라.
쉬운 문제 이므로 풀이는 생략한다. 정답은 아래와 같다.
int (*(*p[42][10][53][65])[10])[2][10];
문제 3.
크기 9×6×4짜리 3차원 배열이 있다.이 배열의 각 원소는 크기 5×3짜리 double 배열 2개를 가리키는 포인터 4개로 이루어진 배열이다. 이 전체 구조를 가리키는 포인터를 선언하라.
double (*(*(p[9][6][4])[4])[2])[5][3]
문제 4.
크기 7×12×8×6짜리 4차원 배열이 있다.이 배열의 각 원소는 함수 포인터 5개로 이루어진 배열이다.
각 함수는 매개변수로 'int 3×4짜리 배열을 가리키는 포인터 2개짜리 배열의 포인터'를 받는다.
각 함수는 'double 9×2짜리 배열을 가리키는 포인터'를 반환한다.
이 전체 구조를 가리키는 포인터를 선언하라.
제일 낮은 기본적인 함수 포인터 부터 만들어봅시다.
매개변수로 int (*(*)[2])[3][4] 타입을 받는다고 정의되어 있습니다.
그리고 함수 포인터는 double(*)[9][2] 타입을 리턴한다고 되어 있습니다.
이를 함수 포인터로 나타내면 다음과 같습니다.
double(* (*f) (int (*(*)[2])[3][4]) )[9][2];
우리는 어떠한 타입이 존재할 때, 그것을 배열로써 선언하는 것은 변수오른쪽에 대괄호연산자만 붙여주면 된다는 것을 알고 있습니다. 함수 포인터도 똑같습니다. 함수 포인터도 하나의 타입입니다. 변수의 오른쪽에 대괄호연산자를 통해 배열로 선언합니다.
double(* (*f[7][12][8][6]) (int (*(*)[2])[3][4]))[9][2];
문제 5.
크기 4×7×3×9×2짜리 5차원 배열이 있다.이 배열의 각 원소는 const char 5×8짜리 배열을 가리키는 포인터 6개로 이루어진 배열 3개를 가리키는 포인터다.그리고 이 5차원 배열을 가리키는 포인터의 포인터를 선언하라.
const char (*(*(*(**p)[4][7][3][9][2])[3])[6])[5][8];
문제 6.
크기 5×11×7×9×3×8짜리 6차원 배열이 있다. 이 배열의 각 원소는 함수 포인터 7개로 이루어진 배열이다.
각 함수는 매개변수로 다음을 받는다:
'int 4×6짜리 배열을 받아서 const volatile double 2×5짜리 배열을 가리키는 포인터를 반환하는 함수 포인터' 3개로 이루어진 배열의 포인터
각 함수는 다음을 반환한다:
'float 8×3×2짜리 배열을 가리키는 포인터' 4개로 이루어진 배열을 가리키는 포인터
이 전체 구조를 가리키는 포인터의 포인터를 선언하라.
저는 이 문제가 가장 어려웠던 것 같습니다. 일단 가장 작은 단위, 부터 생각을 해보자면....
포인터 함수의 매개변수로 포인터 함수를 받고 있으니, 그 매개변수 부터 구현하여, bottom 방식으로 올라가는게 좋다고 생각합니다.
const volatile double (*(*p)(int [4][5]))[2][5] 이것이 안쪽 파라미터로 들어가는 함수 포인터입니다.
이것이 3개로 이루어진 배열의 포인터가 필요하므로, 변수의 오른쪽에 [3] 붙이고 포인터화 시키면 됩니다.
const volatile double (*(*(*p)[3])(int [4][5]))[2][5]; 이렇게 우리는 함수 포인터의 파라미터로 들어가는 함수 포인터를 구현 했습니다.
함수 포인터는 float (*(*f)[4])[8][3][2] 타입을 리턴합니다. 따라서, 이것을 위쪽에 선언한 타입을 파라미터로 집어넣어서, 함수 포인터화 해봅시다.
float (*(*(*f) (const volatile double (*(*(*)[3])(int [4][5]))[2][5] ) )[4])[8][3][2]; 해당 타입이 7개로 이루어진 배열이 6차원 배열의 원소가 되므로, 그 원소 타입은 아래와 같습니다.
float (*(*(*f[7]) (const volatile double (*(*(*)[3])(int [4][5]))[2][5] ) )[4])[8][3][2];
그리고, 지금 타입을 문제에 나온 6차원 배열로 전개합니다.
float (*(*(*(f [5][11][7][9][3][8] )[7]) (const volatile double (*(*(*)[3])(int [4][5]))[2][5] ) )[4])[8][3][2]; 이 타입의 포인터의 포인터를 가져옵니다.
float (*(*(*((**f) [5][11][7][9][3][8] )[7]) (const volatile double (*(*(*)[3])(int [4][5]))[2][5] ) )[4])[8][3][2]; 가 정답이 됩니다.
문제 7.
크기 4×6×8짜리 3차원 배열이 있다.
이 배열의 각 원소는 '함수 포인터를 반환하는 함수 포인터' 5개로 이루어진 배열이다.
각 함수는 매개변수로 'const char 7×9짜리 배열의 포인터'를 받는다.
각 함수는 '매개변수 없이 volatile int 3×4×5짜리 배열의 포인터를 반환하는 함수 포인터'를 반환한다.
이 전체를 가리키는 포인터를 선언하라.
문제 6에서는 함수의 포인터가 파라미터로 들어온 반면, 이번에는 함수의 포인터가 함수의 포인터의 리턴값으로 나가고 있습니다.
매우 깊숙한 원소부터 만들어봅시다…
'매개변수 없이 volatile int 3×4×5짜리 배열의 포인터를 반환하는 함수 포인터' 를 만들면..
volatile int (*(*f)())[3][4][5]; f가 함수 포인터가 되게 됩니다.
f를 반환하는 매개변수로 const char 7x9를 받는 함수포인터는 만들기 쉽죠.
이거 그대로 선언하면 decay 되어 매개변수에는 const char (*)[5]가 들어가지만, 지금은 그냥 넘어가도록 하겠습니다.
volatile int (((*f)(const char [7][5]) )())[3][4][5]; 입니다.
f가 5개로 이루어진 배열.
volatile int (((*f[5])(const char [7][5]) )())[3][4][5]; 해당 타입을 원소로가지는 [4][6][8] 배열.
volatile int (((*(f[4][6][8])[5])(const char [7][5]) )())[3][4][5]; 이 정답이다.
이번 포스팅에서는 C 언어를 공부하면서 타입 캐스팅에 대해 어떻게 학습했는지, 그리고 이를 실제로 문제를 만들어보며 익혀나간 과정을 공유드렸습니다.
특히 함수형 포인터와 일반 배열 포인터(array pointer)에 대해 보다 깊이 이해할 수 있도록 노력했던 제 경험이 독자 여러분께도 도움이 되었기를 바랍니다.
이 글은 여기서 마치도록 하겠습니다. 이제 시험을 보러 가보겠습니다. 감사합니다.
'황현석 어록' 카테고리의 다른 글
| 컴퓨터 구조 - 고찰편 (1) | 2025.12.17 |
|---|---|
| 객체 지향 설계 프로그래밍 C++, 중간 겸 고찰 (4) | 2025.10.20 |
| 충남대학교 2025 1학기를 마치며... (3) | 2025.06.18 |
| 2025 ACPC 참가 후기 (3) | 2025.05.27 |
| 알고리즘, 템플릿을 정리하고 있습니다. (0) | 2024.07.18 |