안녕하세요, 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와 어떤 것에 대한 포인터라는 것을 직관적으로 알 수 있습니다.
뭐 허튼, 이번 글은 여기까지로 하겠습니다. 오역/오타 등의 피드백 모두 받습니다. 감사합니다.
-
(역자 주) 직역하면 시계 방향/나선형 규칙입니다. ↩