C언어 복잡한 선언 해석

MySprtlty·2023년 2월 3일
0

C

목록 보기
8/37

C언어 선언은 굉장히 복잡하다.
그러나 해석하는 법만 제대로 학습하면, C언어의 어떤 선언이든 쉽게 읽어낼 수 있다.

void (*siganl(int sig, void (*func)(int)))(int);

여기서 signal의 데이터 형을 온전하게 유도해낼 수 있는가?
위의 선언을 해석해 낼 수 없다면, 이 글을 한 번 읽어보는 것을 추천한다.
읽다가 이해가 안되더라도 일단 넘어가며 읽어보길 추천한다.
적용 단계에서 하나 둘씩 이해가 될 것이다.
하지만, 빨간색으로 강조한 punctuator 읽는 법은 꼭 암기해야 한다.

🏷️C언어 복잡한 선언 해석

  • C언어의 선언은 꽤 복잡한 편에 속한다.
  • 하지만 몇가지 규칙을 기억하고, 연습만하면 어떤 C언어 선언이든 기계적으로 해석해낼 수 있다.
  • 먼저 선언에 나오는 punctuator(구분자)를 어떻게 읽는지 알아본다.

📌punctuator 읽는 법

1. *

  • 포인터를 의미하는 punctuator
  • pointer to ~

2. ()

  • 함수를 의미하는 punctuator
  • function returning ~ 라고 읽는다.
  • 다만 parameter가 존재한다면, ()사이에 parameter의 type name(데이터형명)을 적어준다.
    • 🔍ex) void foo(int size);function(int) returning void가 된다.

3. []

  • 배열을 의미하는 punctuator
  • array of ~
  • [n]으로 선언됐다면, array of n ~이라고 적어준다.
    • 🔍ex) int arr[4];array of 4 int가 된다.

📌punctuator 우선순위

  • 명칭(식별자)에 묶이는 우선순위는 다음과 같다.
  • ()=[]>*
  • 즉, 언제나 배열과 함수를 의미하는 punctuator([], ())가 포인터를 의미하는 punctuator(*)보다 명칭과 먼저 묶인다.

📌적용

1. 포인터

int *pi;
  • pi의 데이터 타입은 pointer to int다.

2. 함수

int foo(void);
void bar(int);
  • foo의 타입은 function returning int다.
  • bar의 타입은 function(int) returning void다.

3. 배열

int arr[5];
  • arr의 타입은 array of 5 int다.

4. 포인터 + 함수

int *foo(void);
  • 위에서 언급했듯, ()우선순위가 *보다 높으므로 명칭 foo와 먼저 묶인다.
  • 그러면, foo의 타입은 function returning ~ 형태로 시작한다.
  • 명칭 foo*가 묶인다.
  • 따라서 foo의 타입은 function returning pointer to int가 된다.

5. 포인터 + 배열

int *arr[5];
  • 우선순위가 높은 []가 먼저 명칭 arr와 묶인다.
  • 따라서 array of 5 ~ 형태로 시작한다.
  • 그 후 *와 묶인다.
  • 따라서 arr의 타입은 array of 5 pointer to int가 된다.

6. 포인터 + 함수 + 배열

int *handler[5](void); /* wrong */
  • 명칭 handler 양 옆에 있는 * vs [][] 우선순위가 더 높다.
  • 따라서 array of 5 ~로 형태로 시작한다.
  • 그 다음 * vs ()를 비교해야 하는데, 규칙에 따라 ()우선순위가 높다.
  • 따라서 array of 5 function returning ~이 된다.
  • 그리고 남은 *와 결합해서 array of 5 function returning pointer to int된다.
  • 그러나 C언어 문법상 위 선언은 잘못된 선언이다.
  • C는 함수형을 배열의 요소로 가질 수 없다.
    • 🖇️cf. 함수도 하나의 데이터 타입이다.
  • 다만 함수를 가리키는 포인터(pointer to function)는 요소로 가질 수 있다.
  • 따라서 다음과 같이 수정하면 올바른 선언이 된다.

int (*handler[5])(void); /* correct */
  • 괄호 연산자()로 결합되는 우선순위를 바꿀 수 있다.
  • *handler[5]를 둘러 싼 ()는 함수를 의미하는 punctuator가 아닌 괄호 연산자임을 주의해야 한다.
  • 이제 위 선언을 해석해보자.
  • 우선순위가 높은 []가 먼저 명칭 handler와 묶이므로, array of 5 ~로 시작한다.
  • 그 다음 괄호 연산자에 의하여 *와 먼저 묶이므로, array of 5 pointer to ~가 된다.
  • 마지막으로 ()와 묶여 handler의 타입은 array of 5 pointer to function returning int가 된다.

7. Type Qualifier가 포함된 선언

  • 🖇️cf. const, volatile, restrict같은 키워드를 Type Qualifier라고 한다.
  • 이들과 결합되면 하나의 새로운 데이터 타입이 된다.
  • intconst int 그리고 const volatile int모두 구분되는 데이터 타입이다.
const int * const cpci = (void *)0; 
  • cpci의 데이터 타입은 const pointer to const int가 된다.

8. signal 해석

void (*siganl(int sig, void (*func)(int)))(int);
  • 이제 처음에 언급했던 signal명칭이 과연 어떤 데이터 타입인지 해석해보자.
  1. siganl이 우선순위 규칙에 따라 ()와 먼저 결합된다.
  2. function returning ~이 된다.
  3. 그런데 이 함수는 parameter를 갖는 함수다.
  4. 첫 번째 parameter sig의 타입은 int다.
  5. 두 번째 parameter는 void (*func)(int))로 조금 복잡한데, 이 것도 지금까지 배운 방식대로 해석해내면 된다.
  6. func의 타입은 pointer to function(int) returning void가 된다.
  7. 다시 처음으로 돌아오면 signalfunction(int, pointer to function(int) returning void) returning ~형태가 된다.
  8. 이제 signal은 괄호 연산자()에 의해 *와 결합된다.
  9. function(int, pointer to function(int) returning void) returning pointer to ~가 된다.
  10. 마지막으로 ()와 결합된다.
  11. signal의 타입은 function(int, pointer to function(int) returning void) returning pointer to function(int) returning void다.

📌최종유도형

  • 가장 마지막으로 명칭에 유도되는 데이터 타입을 의미한다.
    • 🖇️cf. 처음으로 명칭과 묶이는 데이터 타입
    • 🖇️cf. declaration specifier부터 유도되기 시작하기에 최종유도형이라고 부른다.
  • 즉, 다음과 같은 질문의 답이 최종 유도형이다.

"그래서 결국 그 명칭의 데이터 타입이 뭔데?"

  • signal은 굉장히 복잡한 선언을 갖지만, 결국 어찌됐든 함수(function)다.
  • 즉, signal의 최종유도형을 함수라고 한다.

여담이지만, C++에서 & punctuator는 reference to ~로 읽으면 된다.

0개의 댓글