안녕하세요, static입니다. 굉장히 오랜만에 글을 작성하네요. 이 글은 이 사이트를 번역한 글입니다. 의역이 많이 포함되어 있습니다.
뱅뱅 돌려 읽기 기법1으로 알려진 C/C++에서 복잡한 선언문을 이해하는 방법이 있습니다. 다음의 간단한 3가지 규칙을 따르면 됩니다.
- 의미를 알고자 하는 식별자로부터 시작하고, 다음에 열거된 토큰을 만나면 다음 한국어 문장으로 만듭니다.
[N]
또는[]
:N
개의 ~를 가진 또는 크기가 정해지지 않은 배열(typeA, typeB, ...)
:typeA
와typeB
를 받아 ~를 반환하는 함수*
: ~의 포인터
- 모든 토큰을 만날 때까지 계속 진행합니다.
- 항상 괄호 안에 있는 토큰부터 풉니다.
예제 1: 간단한 선언문
+-------+
| +--+ |
| ^ | |
char* str[10];
^ ^ | |
| +----+ |
+----------+
str
은 무엇일까요?
str
에서 시작합니다. 가장 먼저 만나는 토큰은[
입니다. 이는 배열임을 의미합니다.
“str
은 10개의 ~를 가진 배열- 계속 시계 방향으로 진행합니다. 그러면 우리가 만나는 토큰은
*
입니다. 이는 포인터를 가짐을 의미합니다.
“str
은 10개의 ~의 포인터를 가진 배열 - 계속 시계 방향으로 진행하면
;
를 만나므로 무시하고, 계속 진행하면char
를 만납니다.
“str
은 10개의char
의 포인터를 가진 배열” - 모든 토큰을 만났으므로 종료합니다.
예제 2: 함수 포인터
+------------------+
| +---+ |
| |+-+| |
| |^ || |
char*(*fp)(int, float*);
^ ^ ^ || |
| | +--+| |
| +-----+ |
+---------------------+
fp
는 무엇일까요?
- 시계 방향으로 진행하면 가장 먼저 만나는 토큰은
)
입니다.fp
는 괄호 안에 있으므로 계속 진행합니다. 그럼 우리가 만나는 토큰은*
입니다. 이는fp
가 포인터임을 의미합니다.
“fp
는 ~의 포인터 - 괄호를 벗어나 시계 방향으로 계속 진행하면
(
를 만납니다. 이는 함수를 가짐을 의미합니다.
“fp
는int
와float*
를 받아 ~를 반환하는 함수의 포인터 - 시계 방향으로 계속 진행하면
*
를 만납니다.
“fp
는int
와float*
를 받아 ~의 포인터를 반환하는 함수의 포인터 - 시계 방향으로 계속 진행하면
;
를 만나지만, 모든 토큰을 만나지 않았으므로 계속 진행합니다. 그러면char
를 만납니다.
“fp
는int
와float*
를 받아char
의 포인터를 반환하는 함수의 포인터”
예제 3: 최종 보스
+----------------------------+
| +---+ |
|+-----+ |+-+| |
|^ | |^ || |
void(*signal(int, void(*fp)(int)))(int);
^ ^ | ^ ^ || |
| +------+ | +--+| |
| +-------+ |
+--------------------------------+
signal
은 무엇일까요? signal
은 괄호 안에 있으므로 주의해야 합니다.
- 시계 방향으로 진행합니다. 우리는
(
를 만납니다.
“signal
은int
와 ~를 받아 ~를 반환하는 함수 - 우리는
fp
에 같은 방법을 적용할 수 있습니다.fp
도 괄호 안에 있으므로, 계속 진행하면*
를 만납니다.
“fp
는 ~의 포인터 - 시계 방향으로 계속 진행하면
(
를 만납니다.
“fp
는int
를 받아 ~를 반환하는 함수의 포인터 - 이제 괄호 밖에서 계속 진행합니다. 그럼 우리는
void
를 만납니다.
“fp
는int
를 받아 아무 것도 반환하지 않는(void
) 함수의 포인터” fp
의 의미를 이해했으므로 다시signal
을 해석합니다.
“signal
은int
와int
를 받아 아무 것도 반환하지 않는 함수의 포인터를 받아 ~를 반환하는 함수- 우리는 여전히 괄호 안에 있으므로
*
를 만납니다.
“signal
은int
와int
를 받아 아무 것도 반환하지 않는 함수의 포인터를 받아 ~의 포인터를 반환하는 함수 - 괄호를 해결했으므로 시계 방향으로 계속 진행하면 또 새로운
(
를 만납니다.
“signal
은int
와int
를 받아 아무 것도 반환하지 않는 함수의 포인터를 받아int
를 받아 ~를 반환하는 함수의 포인터를 반환하는 함수 - 마지막으로 계속 진행하면 우리는
void
를 만나게 됩니다. 따라서signal
의 의미는
“signal
은int
와int
를 받아 아무 것도 반환하지 않는 함수의 포인터를 받아int
를 받아 아무 것도 반환하지 않는 함수의 포인터를 반환하는 함수”
이 방법을 const
와 volatile
에도 적용할 수 있습니다. 예를 들어:
const char* chptr;
chptr
는 무엇일까요?
“chptr
는 변하지 않는(const
)char
의 포인터”
char* const chptr;
chptr
는 무엇일까요?
“chptr
는char
의 변하지 않는 포인터”
volatile char* const chptr;
chptr
는 무엇일까요?
“chptr
는 최적화 되지 않는(volatile
)char
의 변하지 않는 포인터”
굉장히 쉽게 C/C++의 복잡한 선언문을 이해할 수 있는 규칙입니다. C/C++ 프로그래머라면 이 방법 정도는 알아두는 것이 좋겠네요. 사실 이런 사이트도 있습니다. 영어 사이트이긴한데, 선언문을 입력하면 영어 문장으로 만들어 줍니다. 사실 저는 개인적으로 한국어로 이해하는 것보다는 영어로 이해하는 것이 더 이해하기 쉬운 것 같아요. 영어는 무엇이 무엇을 수식하는지 알기 쉬운데, 한국어는 영어에 비해 상대적으로 알기 어려운 것 같은 느낌이 듭니다.
예를 들어, 예제 3의 경우 답(?)이 “signal
은 int
와 int
를 받아 아무 것도 반환하지 않는 함수의 포인터를 받아 int
를 받아 아무 것도 반환하지 않는 함수의 포인터를 반환하는 함수”였는데, “int
와 int
를 받아 아무 것도 반환하지 않는 함수의 포인터”가 매개 변수가 int
, int
인지 int
, void(*)(int)
인지 애매한데, 영어로 하면 “passing an int and a pointer to a function passing an int returning nothing (void)”로, 한국어에 비해 구분이 잘 됩니다. “int and a pointer to …” 라서 int와 어떤 것에 대한 포인터라는 것을 직관적으로 알 수 있습니다.
뭐 허튼, 이번 글은 여기까지로 하겠습니다. 오역/오타 등의 피드백 모두 받습니다. 감사합니다.
-
(역자 주) 직역하면 시계 방향/나선형 규칙입니다. ↩