Java 11
증감 연산자와 복합 대입: 값 변화 시점 예측
++와 +=는 짧아 보이지만 값이 언제 변하는지 헷갈려서 출력 결과를 예측하기 어렵다.
근거 · 교안 p78-p82
1장: 연산자의 ‘언제’가 만드는 혼란
솔라의 손가락이 노트북 트랙패드 위에서 멈췄다. 화면에는 몇 줄 안 되는 코드 예제가 떠 있었다. 분명 ‘기초’라고 적힌 페이지였지만, 솔라의 미간은 펴질 줄을 몰랐다. 값을 1 증가시키는 ++ 연산자, 기존 값에 다른 값을 더해 대입하는 += 연산자. 생긴 것도 간단하고 하는 일도 명확해 보였다. 하지만 댓글창에서는 이 간단한 연산자들의 실행 결과를 두고 작은 논쟁이 벌어지고 있었다.
“분명 1을 더하는 건데… 왜 결과가 다르다는 거지?”
혼잣말을 들었는지, 방으로 들어오던 루나가 솔라의 노트북 화면을 흘깃 보았다.
“아, 그 녀석들. 짧고 편해 보이지만 은근히 까다롭지.”
“언니, 봐봐. a++ 나 a += 1 이나 결국 a에 1을 더하는 거잖아. 그런데 어떤 사람은 5가 나온다 하고, 다른 사람은 6이 나온다고 해. 코드가 어떻게 쓰였는지에 따라 다르다는데, 도대체 ‘언제’ 값이 바뀌는지 모르겠어. 다른 계산이랑 같이 있을 때, 그 계산 전에 바뀌는 거야? 아니면 계산이 다 끝난 다음에?”
솔라의 말에는 답답함이 묻어났다. 간단한 규칙일 거라 생각했는데, 막상 마주하니 안갯속 같았다. 모든 연산자가 그냥 순서대로, 즉시 값을 바꾸면 직관적일 텐데, 그렇지 않은 상황이 있다는 것 자체가 혼란스러웠다.
루나는 말없이 자신의 노트북을 열어 솔라 옆에 앉았다. 그리고는 몇 글자 빠르게 타이핑했다. 화면에 나타난 코드는 극도로 단순했다.
int a = 5;
a++;
System.out.println(a);
“자, 솔라. 이건 어때? 결과가 뭘까?”
솔라는 코드를 보자마자 대답했다.
“변수 a를 5로 만들고, a++로 1을 더하니까 a는 6이 되겠네. 그리고 6을 출력하겠지. 결과는 6.”
“직접 실행해 볼래?”
솔라는 루나의 노트북에서 코드를 실행했다. 콘솔 창에 망설임 없이 숫자 6이 찍혔다. 예상과 정확히 일치했다. 하지만 솔라의 표정은 개운치 않았다.
“이건 너무 간단하잖아. 내 말은, 이게 다른 코드랑 한 줄에 섞여 있을 때가 문제라는 거야.”
솔라는 잠시 망설이더니, 루나가 작성한 코드의 두 번째 줄과 세 번째 줄을 합쳐 버렸다.
int a = 5;
System.out.println(a++);
“만약 이렇게 쓴다면? a++가 println 안에 들어가면 얘기가 달라지잖아. println이 먼저 실행될까, ++가 먼저 실행될까? 만약 println이 먼저라면 화면엔 5가 찍히고 그 뒤에 a가 6으로 바뀌겠지. ++가 먼저라면 화면에 6이 찍힐 거고.”
바로 그거였다. 솔라 스스로가 혼란의 핵심을 짚어낸 순간이었다. 변수의 값이 바뀐다는 사실 자체를 모르는 게 아니었다. 그 변화가 일어나는 정확한 ‘시점’을 예측할 수 없다는 것, 그것이 문제의 본질이었다.
“++나 += 같은 연산자들이 코드를 짧게 만들어줘서 편하라고 있는 것 같은데… 오히려 값이 변하는 타이밍을 예측하기 어렵게 만들어서 더 헷갈리는 것 같아.”
솔라가 마침내 자신의 답답함을 명확한 문장으로 정리해냈다. 처음에는 막연하게 ‘어렵다’고 느꼈지만, 간단한 코드를 직접 비교하고 질문을 던지자, ‘무엇을 모르는지’가 뚜렷해졌다.
루나는 고개를 끄덕였다. 이제 제대로 된 질문이 만들어졌다는 표정이었다.
“맞아. 핵심은 ‘무엇’을 하느냐가 아니라, ‘언제’ 하느냐야. 그리고 솔라 네가 방금 바꾼 코드처럼, 연산자가 변수의 앞에 붙는지 뒤에 붙는지에 따라 그 ‘언제’가 달라져.”
루나는 솔라가 고쳐 쓴 a++ 옆에, ++a라는 글자를 나란히 놓아주었다.
a++ 와 ++a
두 표현 모두 a를 1 증가시킨다. 하지만 솔라의 눈에는 이제 두 기호가 완전히 다르게 보이기 시작했다. 하나는 값을 사용한 ‘후’에, 다른 하나는 값을 사용하기 ‘전’에 뭔가를 할 것만 같았다.
“앞에 붙는 거랑 뒤에 붙는 게… 행동하는 순서가 다르다고?”
솔라의 질문은 다음 혼란의 시작이자, 동시에 명확한 탐구의 출발점이기도 했다.
2장: 전위 증감: 먼저 바꾸고 사용하기
루나의 노트북 화면에는 a++와 ++a가 나란히 커서를 깜빡이고 있었다. 솔라는 두 표현의 미세한 차이를 응시했다. 둘 다 a를 1 증가시키는 건데, 더하기 기호가 앞에 있느냐 뒤에 있느냐의 차이일 뿐이었다. 하지만 방금 전의 대화로 인해, 이 작은 차이가 코드의 실행 순서를 완전히 뒤바꿀 수 있다는 예감이 들었다.
“행동하는 순서가 다르다고?”
솔라의 질문에 루나는 대답 대신 키보드를 두드렸다. 이전 코드를 수정해 새로운 변수 b를 추가하고, ++의 위치를 변수 앞으로 옮겼다. 화면에는 더 명확한 질문을 던지는 코드가 나타났다.
int a = 5;
int b = ++a;
System.out.println("a: " + a + ", b: " + b);
“자, 이번엔 어때? 이 코드의 최종 결과는 어떻게 될까? 변수 a와 b에는 각각 어떤 값이 담겨서 출력될까?”
솔라는 코드를 뚫어지라 쳐다봤다. 이전보다 한 단계 복잡해졌다. ++a라는 연산과, 그 결과를 b에 대입하는 = 연산이 한 줄에 섞여 있었다. 혼란의 핵심을 정면으로 마주한 기분이었다.
‘++a가 먼저일까, b = 가 먼저일까?’
솔라는 머릿속으로 코드의 여정을 그려보았다. “++ 기호가 a의 앞에 있으니까… 일단 a의 값을 먼저 바꾸라는 뜻 아닐까? 그럼 a는 5에서 6이 되고… 그 다음에 그 값을 b에 넣으니까, b에도 6이 들어가겠네. 그럼 a도 6, b도 6이 출력될 것 같아.”
“한번 실행해 봐.”
솔라가 코드를 실행하자, 콘솔에 예상과 같은 결과가 나타났다.
a: 6, b: 6
예측이 맞았지만 솔라의 표정은 여전히 물음표로 가득했다. “맞추긴 했는데, 왜 그런지는 여전히 희미해. 그냥 앞에 있으니 먼저겠거니 하고 찍은 것에 가까워. ‘사용 전에 변화한다’는 말이, = 같은 다른 연산에 사용되기 전에 값이 바뀐다는 뜻이구나… 하고 어렴풋이 짐작할 뿐이야.”
루나는 솔라의 화면 옆에 작은 메모장을 띄우고, 문제의 코드 한 줄을 가져왔다.
int b = ++a;
“컴퓨터가 이 한 줄을 어떻게 읽는지 따라가 보자. 순서는 이래.”
루나는 문장을 단계별로 분해하기 시작했다.
-
++a를 먼저 처리한다.a의 현재 값 5를 1 증가시켜6으로 만든다. 이제 변수a의 상자 안에는6이 들어있다.
-
++a표현식 전체가 하나의 값으로 변한다.++a는 스스로 값을 바꾼 뒤, **바뀐 결과값인6**을 그 자리에 남긴다. 그래서 코드는int b = 6;과 똑같은 상태가 된다.
-
대입 연산(
=)을 실행한다.b에6을 대입한다.
“아!”
솔라의 입에서 짧은 탄성이 터져 나왔다. 루나의 단계별 설명은 안갯속 같던 ‘사용’과 ‘변화’의 의미를 명확하게 갈라주었다.
“그러니까 ++a는 두 가지 일을 동시에 하는 거구나. 첫째, ‘a 너 자신을 1 증가시켜라’는 명령. 둘째, 그 명령이 끝난 후 ‘나는 이제 새로운 값 6이야!’라고 외쳐서, 그 값을 다른 연산이 쓸 수 있게 넘겨주는 역할.”
솔라는 ‘전위(prefix)’라는 이름의 의미를 이제야 제대로 이해할 수 있었다. 변수 앞에 붙어서, 그 변수가 문장의 다른 부분에 **사용되기 전(Pre-)**에 먼저 값을 바꾸는 규칙. 솔라는 자신만의 언어로 규칙을 정리했다.
[전위 선(先)변경 규칙]: ++a를 만나면, 다른 계산보다 먼저 a 값을 바꾼다. 그리고 그 표현식 전체는 방금 바뀐 새로운 값을 결과로 내놓는다.
이제 int b = ++a; 라는 코드는 솔라에게 ‘a를 6으로 만들고, b에도 6을 넣어라’는 단순한 문장이 아니었다. 컴퓨터 내부에서 일어나는 작은 연산들의 정교한 시간 순서가 보이는 듯했다. 값을 먼저 바꾸고, 그 바뀐 값을 현재 문맥에 제공하는 것. 이것이 전위 증감 연산자의 핵심이었다.
명확해지자 곧바로 새로운 궁금증이 고개를 들었다.
“언니, 그럼 만약에… ++가 뒤에 붙는다면? int b = a++; 였다면 결과가 어떻게 달라지는 거야? ‘사용 후에 바꾼다’는 건 대체… b에 5가 들어간다는 뜻인가?”
솔라의 눈은 다시 a++와 ++a가 나란히 적힌 화면으로 향했다. 전위 연산자의 비밀을 풀자, 후위 연산자의 수수께끼가 더욱 궁금해지기 시작했다.
3장: 후위 증감: 사용 후에 바꾸기
솔라의 질문이 채 끝나기도 전에, 루나의 손가락이 키보드 위로 움직였다. 이전 장에서 함께 보았던 코드, int b = ++a; 에서 ++ 기호를 지우더니, 변수 a의 뒤에 다시 찍었다. 화면의 코드는 솔라의 질문을 그대로 반영한 형태로 바뀌어 있었다.
int a = 5;
int b = a++;
System.out.println("a: " + a + ", b: " + b);
커서가 a++ 뒤에서 깜빡였다. 마치 ‘자, 이제 네가 추리해볼 차례야’ 하고 말을 거는 것 같았다. 전위 연산자 ++a의 비밀을 막 풀어낸 솔라에게, 후위 연산자 a++는 완전히 새로운 수수께끼로 다가왔다. 미세한 위치 차이가 어떤 나비효과를 일으킬지, 긴장감이 흘렀다.
“‘사용 후에 바꾼다’는 게… b에 5가 들어간다는 뜻인가?”
솔라는 혼잣말처럼 중얼거리며 자신의 가설을 시험하기 시작했다.
“만약 그렇다면… b = a++ 라는 문장에서, 컴퓨터는 일단 a의 원래 값인 5를 b에 먼저 넣을 거야. 그럼 b는 5가 확실해. 그런데 a는? a는 언제 6으로 바뀌는 거지? b에 5를 넣어주는 일이 끝난 바로 다음에? 아니면 System.out.println까지 전부 실행된, 이 줄이 완전히 끝난 다음에? 음… 일단 b에 값을 주는 임무가 끝났으니, 그 다음에 바로 a의 값을 바꾸지 않을까? 그러면 최종적으로 a는 6, b는 5가 출력될 것 같아.”
추측이었지만 제법 논리적인 흐름이었다. 하지만 솔라는 여전히 확신할 수 없었다. ‘사용 후’라는 말의 정확한 경계가 어디인지 모호했기 때문이다. 그녀는 마른침을 삼키고 실행 버튼을 눌렀다. 잠시 후 콘솔에 나타난 결과는 자신의 예측과 정확히 일치했다.
a: 6, b: 5
“맞았다! b는 5, a는 6. 그런데… 왜지? ++a 때랑은 완전히 다른 결과잖아.”
예측이 맞았다는 기쁨도 잠시, 솔라는 더 깊은 궁금증에 빠졌다. 왜 똑같이 1을 더하는데, 기호의 위치만으로 이렇게 다른 결과가 나오는지, 그 내부 동작 원리를 완벽히 이해하고 싶었다.
루나는 이전처럼 메모장을 띄워 int b = a++; 코드를 가져왔다. 그리고 이번에도 컴퓨터의 시선으로 문장을 분해하기 시작했다.
“이번에도 순서대로 따라가 보자. 전위 연산자와 무엇이 다른지 비교하면서 보면 더 명확할 거야.”
-
a++표현식을 마주한다.- 컴퓨터는
++가 변수 뒤에 붙은 ‘후위(postfix)’ 형태인 것을 확인한다. - 후위 연산의 첫 번째 규칙: 현재 변수의 값을 그대로 사용한다.
a의 현재 값은5이므로,a++라는 표현식 덩어리는 일단 값5가 된다. 코드는 순간적으로int b = 5;처럼 행동한다.
- 컴퓨터는
-
대입 연산(
=)을 실행한다.- 1번 단계에서
a++가 내놓은 값5를 변수b에 대입한다. 이제b의 상자 안에는5가 담겼다.
- 1번 단계에서
-
표현식의 ‘사용’이 끝난 후,
a의 값을 변경한다.a의 값을b에 넘겨주는 ‘사용’이 끝났으므로, 이제++의 임무를 수행한다.a의 값을 1 증가시켜6으로 만든다.
“아…!”
솔라는 무릎을 탁 쳤다. 전위 연산자와의 결정적인 차이점이 보이는 순간이었다.
“전위 연산자 ++a는 스스로를 먼저 6으로 바꾼 뒤에 ‘나는 6이야!’라고 외쳐서 주변에 값을 알렸는데, 후위 연산자 a++는 ‘내 지금 값은 5야! 일단 이거 먼저 써!’라고 외친 다음에, 그 일이 끝나고 나서야 조용히 자신을 6으로 바꾸는 거구나.”
마치 임무를 수행하는 두 요원의 스타일 차이 같았다. 한 명은 자기 상태를 먼저 바꾸고 보고하는 타입(++a), 다른 한 명은 현재 상태를 먼저 보고한 뒤 자신의 상태를 바꾸는 타입(a++).
솔라는 방금 깨달은 내용을 이전 장에서 정리했던 ‘전위 선(先)변경 규칙’ 옆에 나란히 적어 내려갔다. 두 규칙을 비교하니 ‘언제’의 비밀이 선명하게 드러났다.
[후위 후(後)변경 규칙]: a++를 만나면, a의 현재 값을 먼저 다른 연산에 사용하도록 내놓는다. 그 사용이 끝나면 a 자신의 값을 1 증가시킨다.
이제 a++와 ++a는 솔라에게 더 이상 헷갈리는 기호가 아니었다. 값을 바꾸는 시점이 명확히 다른, 두 개의 독립된 규칙으로 자리 잡았다.
규칙이 명확해지자, 솔라의 생각은 자연스럽게 처음의 궁금증으로 돌아갔다. 그녀는 1장에서 처음 헷갈렸던 ++와 += 연산자를 떠올렸다.
“언니, 이제 ++ 연산자의 전위, 후위는 확실히 알겠어. 그럼 += 같은 복합 대입 연산자는 어때? a += 3 같은 건 a++처럼 ‘사용 후 변경’ 같은 복잡한 타이밍 규칙이 있는 거야, 아니면 그냥 a = a + 3을 줄여 쓴 것처럼 단순하게 동작하는 거야?”
증감 연산자의 비밀을 풀자, 비슷해 보이지만 다른 연산자들의 동작 방식이 새로운 궁금증으로 떠올랐다. 이 짧은 기호들 속에 또 다른 시간의 규칙이 숨어 있을지 모른다는 예감이 들었다.
4장: 복합 대입: 축약된 대입일 뿐
솔라의 마지막 질문이 공중에 맴돌았다. 증감 연산자(++)의 비밀을 밝혀내자, 비슷하게 생긴 += 연산자도 같은 종류의 복잡한 시간 규칙을 숨기고 있을 거라는 의심이 피어오른 참이었다.
루나는 솔라의 질문에 대답하는 대신, 키보드를 가져가 화면을 둘로 나누었다. 그리고 양쪽에 간결한 코드 블록을 하나씩 타이핑했다. 두 블록은 거의 똑같아 보였지만, 값을 바꾸는 방식에서 미묘한 차이를 보였다.
왼쪽 화면:
int a = 5;
a += 3;
System.out.println(a);
오른쪽 화면:
int b = 5;
b = b + 3;
System.out.println(b);
두 개의 코드가 나란히 놓이자, 솔라의 질문은 더 이상 막연한 궁금증이 아니었다. a += 3과 b = b + 3이라는, 명확한 비교 대상이 눈앞에 나타났다. 솔라는 왼쪽의 += 기호를 뚫어지라 쳐다봤다. 전위와 후위 증감 연산자를 겪고 난 후라, 모든 짧은 기호가 의심스러웠다.
“언니 말은, 이 두 코드의 결과가 다를 수도 있다는 거야?”
솔라는 자신의 의심을 드러냈다.
“오른쪽 코드는 확실해. b는 5로 시작하고, 5 + 3의 결과인 8이 계산된 다음, 그 8이 다시 b에 저장되니까 최종 결과는 8이겠지. 그런데 왼쪽은… a += 3… 이것도 a++처럼 ‘사용 후 변경’ 같은 규칙이 숨어 있을지도 몰라. 예를 들어, 이 문장이 다른 복잡한 계산의 일부라면, a의 원래 값인 5를 먼저 사용하고 나서야 3을 더하는 식으로 동작할 수도 있잖아. 겉보기엔 똑같이 8이 나올 것 같으면서도, 왠지 불안해.”
마치 ++ 연산자에서 겪었던 뒤통수를 또 맞을까 봐 경계하는 모습이었다. 짧게 줄여 쓴 표현에는 반드시 무언가 숨겨진 동작 원리가 있을 것이라는 강력한 예감이 솔라를 사로잡고 있었다.
루나는 아무 말 없이 양쪽 코드 블록을 가리켰다. 직접 확인해보라는 무언의 신호였다. 솔라는 먼저 왼쪽 코드를 실행했다. 콘솔에 숫자 8이 찍혔다. 이어서 오른쪽 코드를 실행했다. 역시 숫자 8이 찍혔다.
결과는 완벽히 동일했다.
“어? 똑같네…?”
솔라는 허탈한 표정을 지었다. 뭔가 복잡하고 심오한 시간의 규칙이 숨어 있을 거라 예상했는데, 결과는 너무나도 단순했다.
“그럼… a += 3은 그냥 a = a + 3을 줄여 쓴 것뿐이라고? 전위, 후위 같은 복잡한 타이밍 규칙이 전혀 없는 거야?”
“바로 그거야.”
루나가 처음으로 입을 열었다.
“++나 -- 같은 증감 연산자는 ‘값을 사용’하는 시점과 ‘값을 변경’하는 시점이 분리된 특별한 녀석들이야. 그래서 변수 앞이냐 뒤냐에 따라 행동이 달라지지. 하지만 +=, -=, *=, /= 같은 복합 대입 연산자들은 달라. 걔들은 그런 복잡한 성격을 가진 게 아니야. 그냥 코드를 조금 더 짧게 쓰게 해주는 ‘별명’ 같은 거지.”
루나의 설명에 솔라의 머릿속에서 안개가 걷히는 기분이었다. 그녀는 두 연산자의 정체성이 근본적으로 다르다는 것을 깨달았다. ++는 ‘언제’ 행동하는지가 중요한, 독특한 동작 방식을 가진 연산자였다. 반면 +=는 그저 기존의 연산(+)과 대입(=)을 합쳐놓은 축약 표현일 뿐, 새로운 동작 규칙을 만들지 않았다.
“아…! 속았다. 생긴 게 비슷해서 같은 부류인 줄 알았어.”
솔라는 웃음을 터뜨렸다. “++가 행동 방식이 까다로운 특별 요원이라면, +=는 그냥 이름표를 줄여서 단 일반 직원 같은 거네. 내가 어떤 코드를 만나든 a += 3이 보이면, 그냥 머릿속에서 a = a + 3으로 풀어서 생각하면 되는 거구나.”
스스로 규칙을 발견하고 정리한 순간이었다. 복잡할 것이라는 오해가 사라지자, 그 자리에 명확한 원칙이 들어섰다.
[복합 대입 확장 규칙]: a += 3과 같은 복합 대입 연산자를 만나면, 숨겨진 타이밍을 의심할 필요 없이 a = a + 3과 같이 긴 형태로 풀어서 해석한다.
이제 솔라는 ++와 +=가 짧다는 공통점 외에는 완전히 다른 존재임을 이해했다. 값의 변화 ‘시점’을 예측해야 하는 증감 연산자와, 단순히 ‘식을 축약’한 복합 대입 연산자를 명확히 구분할 수 있게 된 것이다.
하나의 산을 넘자, 더 큰 산이 눈앞에 나타났다. 각각의 연산자를 따로따로 이해하고 나니, 자연스럽게 이들이 한데 섞였을 때 어떤 일이 벌어질지 궁금해졌다.
“좋아, 언니. 이제 ++a, a++, 그리고 a += 3 각각은 알겠어. 그럼 만약에… 만약에 이 모든 게 한 줄에 같이 나오면 어떻게 되는 거야? 예를 들어 int y = x++ + ++x; 같은 코드 말이야. 이럴 땐 대체 어떤 순서로 계산해야 해? 오른쪽 ++x가 먼저 x를 바꾸고, 그게 왼쪽 x++에 영향을 줘?”
개별 부품의 작동법을 익힌 기술자가, 이제 그 부품들로 조립된 복잡한 기계의 전체 동작 순서를 궁금해하는 눈빛이었다. 안갯속 같던 혼란이 걷히자, 더 정교하고 어려운 질문이 모습을 드러내고 있었다.
5장: 복합 표현식: 단계별 값 변화 추적
루나는 솔라의 질문에 말로 대답하는 대신, 조용히 키보드를 가져갔다. 이전 장에서 솔라가 마지막에 던졌던, 개별 부품이 한데 얽힌 복잡한 기계의 설계도 같은 질문. 그 추상적인 공포를 루나는 화면 위에 구체적인 현실로 옮겨 적기 시작했다.
int x = 10;
int y = x++ + ++x;
System.out.println("x: " + x + ", y: " + y);
커서가 코드의 마지막에서 깜빡였다. 마치 ‘자, 이제 이 괴물을 직접 해부해 봐’라고 도전장을 내미는 듯했다. 솔라는 미간을 찌푸렸다. 산술 연산자 우선순위에 따르면 덧셈보다 증감 연산이 먼저일 것이다. 하지만 한 줄에 같은 변수에 대한 증감 연산이 두 번이나 나타나니, 어느 쪽부터 처리해야 할지 감조차 오지 않았다.
“음… 일단 컴퓨터는 왼쪽부터 읽으니까… x++가 먼저겠지? 이건 후위 연산이니까, 일단 x의 현재 값인 10을 사용하고, 그 다음에 x는 11이 될 거야. 그리고… 오른쪽의 ++x를 만나. 이건 전위 연산이니까 x를 먼저 바꿔야 해. x는 11이었으니까 12가 되고, 바뀐 값 12를 사용하겠네. 그럼 y에 들어가는 값은 결국 10 + 12가 되어서 22인가? 최종 x는 12가 되고.”
솔라는 자신의 추리를 중얼거렸지만, 목소리에는 확신이 없었다. 마치 젠가 블록을 아슬아슬하게 쌓는 기분이었다. 순서 하나만 틀려도 와르르 무너질 것 같은 불안감.
“근데 만약에 컴퓨터가 덧셈 기호 오른쪽의 ++x를 먼저 처리하면 어떡해? 그럼 x가 11이 되고, 그 값을 사용하고… 남은 왼쪽의 x++는 11을 사용한 뒤 x를 12로 만들고… 그럼 11 + 11이 되어서 y는 22… 어? y는 똑같이 22네? 하지만 중간 과정이 완전 다르잖아. 어느 쪽이 맞는 거야? 이건 그냥 운 좋게 답이 같은 거고, 다른 코드에선 완전히 틀릴 수도 있겠는데?”
결과를 맞히는 것과, 그 결과에 도달하는 과정을 명확히 아는 것은 다른 차원의 문제였다. 솔라는 연산자 우선순위만으로는 이 시간의 흐름을 예측할 수 없다는 걸 깨달았다.
루나는 솔라의 혼란스러운 독백을 듣고는, 화면 옆에 작은 메모장을 띄웠다. 그리고는 표의 머리글처럼 보이는 몇 개의 단어를 적었다.
| 단계 | 실행 코드 | x 값 | 계산에 사용할 값 |
표가 만들어지자, 루나는 문제의 코드 y = x++ + ++x;를 가리켰다.
“한 번에 전부 예측하려 하지 말고, 컴퓨터가 되어서 한 걸음씩만 가보자. 컴퓨터는 덧셈 같은 연산을 하기 전에, 양쪽에 있는 재료들(피연산자)의 값을 먼저 확정해. 중요한 규칙은, 왼쪽 재료부터 순서대로 준비한다는 거야.”
루나는 ‘단계 1’이라고 적고는 솔라를 쳐다봤다. 솔라는 그 눈빛의 의미를 알아채고 펜을 들었다. 이제부터는 막연한 추리가 아닌, 정교한 추적의 시간이었다.
1단계: 왼쪽 항 x++의 값을 계산한다.
솔라는 표에 적기 시작했다. “x는 현재 10. x++는 후위 연산. ‘먼저 사용하고, 나중에 바꾼다’는 규칙에 따라, ‘계산에 사용할 값’은 현재 값인 10이 된다.”
솔라는 표의 마지막 칸에 10을 적었다.
2단계: x++의 부수 효과(side effect)를 적용한다.
“‘사용’이 끝났으니 x 값을 1 증가시켜야 해. x는 11이 된다.”
솔라는 ‘x 값’ 칸을 10에서 11로 갱신했다. 한 줄에 있지만, x의 값은 이미 변해 있었다.
3단계: 오른쪽 항 ++x의 값을 계산한다.
“이제 오른쪽 차례. 코드는 ++x고, 현재 x 값은 11이야. 이건 전위 연산. ‘먼저 바꾸고, 나중에 사용한다’ 규칙이지. 그러니까 x를 12로 먼저 바꾼다.”
솔라는 ‘x 값’ 칸을 다시 11에서 12로 갱신했다.
“그리고 바뀐 값 12를 ‘계산에 사용할 값’으로 내놓는다.”
솔라는 표의 마지막 칸에 12를 적었다.
4단계: 모든 재료 준비 완료. 덧셈(+)을 실행한다.
이제 덧셈 연산자는 자기 차례를 맞았다. 왼쪽 재료는 10, 오른쪽 재료는 12로 확정되었다.
“10 + 12는 22. 이 값을 y에 대입한다.”
솔라는 손을 멈추고 자신이 완성한 표를 물끄러미 내려다보았다.
| 단계 | 실행 코드 | x 값 | 계산에 사용할 값 |
|---|---|---|---|
| 시작 | 10 | ||
| 1 | x++ 평가 | 10 | 10 (현재 값 사용) |
| 2 | x++의 증가 | 11 | |
| 3 | ++x 평가 | 12 | 12 (바뀐 값 사용) |
| 4 | + 와 = | 12 | y = 10 + 12 = 22 |
“아…”
탄성과 함께 안개가 걷혔다. 연산자 우선순위는 그저 ++가 +보다 먼저 ‘평가’된다는 큰 틀일 뿐, 진짜 열쇠는 한 문장 안에서 코드가 왼쪽에서 오른쪽으로 평가되면서 변수 값이 실시간으로 변해가는 흐름을 놓치지 않는 것이었다.
“흐름을 놓치면 안 되는 거였구나. 한 줄의 코드라도, 그 안에는 여러 단계의 시간 순서가 숨어 있었어.”
솔라는 스스로 발견한 이 추적 방법을 써보고 싶어졌다. 그녀는 아까 자신을 혼란스럽게 했던, 순서가 바뀐 코드를 메모장에 새로 적었다.
int y = ++x + x++; (x는 10에서 시작)
이번에는 루나의 도움 없이, 혼자 힘으로 표를 그려나가기 시작했다.
| 단계 | 실행 코드 | x 값 | 계산에 사용할 값 |
|---|---|---|---|
| 시작 | 10 | ||
| 1 | ++x 평가 | 11 | 11 (바뀐 값 사용) |
| 2 | x++ 평가 | 11 | 11 (현재 값 사용) |
| 3 | x++의 증가 | 12 | |
| 4 | + 와 = | 12 | y = 11 + 11 = 22 |
표를 완성한 솔라는 결과를 보고 놀랐다. y의 최종값은 22로 이전과 같았지만, 더해지는 두 수는 11과 11로 완전히 달랐다. 만약 이 표와 같은 ‘연산 흐름 추적’이 없었다면, 왜 y가 같은 값이 나왔는지 평생 이해하지 못했을 것이다.
솔라는 문득 처음 외웠던 규칙을 떠올렸다. “전위 증감은 먼저 값을 바꾸고, 후위 증감은 현재 값을 사용한 뒤 바꾼다.” 그 규칙은 틀리지 않았다. 하지만 그것만으로는 한 줄에 여러 연산이 얽힌 문장의 전체 그림을 볼 수 없었다. 이 ‘연산 흐름 추적’이라는 지도가 있어야만, 각 규칙이 왼쪽에서 오른쪽으로 진행되는 흐름 속 어느 시점에, 어떤 순서로 적용되는지 비로소 명확하게 보였다.
이제 그녀는 어떤 복잡한 연산 코드를 만나더라도, 겁먹지 않고 차분히 그 흐름을 단계별로 추적해 나갈 자신감을 얻었다. 짧은 기호들 속에 숨겨진 시간의 규칙을 읽어내는 자신만의 지도를 손에 넣은 것이다.