본문 바로가기
Topos Uranos

대입, 부수 효과, 정의되지 않은 동작

요약

C++의 식은 서로 다른 두 가지 일을 할 수 있다. 값을 내는 일과 프로그램의 상태를 바꾸는 일이다. x + 1 식은 값만 낸다. x = 7 식은 값을 내고, 그에 더해 x 변수를 바꾼다. x++ 식은 x 변수가 가졌던 값을 내고, x 변수에는 다른 값을 남긴다. 이 수업은 식의 값과 효과의 이 차이를 공부한다. 식으로서의 대입, 복합 대입, 전위형과 후위형의 증가와 감소, 그리고 식의 각 부분이 계산되는 순서이며, 이 순서는 표준이 일부만 정한다. 여기서 수업의 나머지를 이끄는 물음이 나온다. 표준은 프로그램에 대해 무엇을 약속하는가. 그 답은 정의된 동작에서 정의되지 않은 동작까지 네 등급으로 이루어진 목록으로, 지금까지 본 모든 경우를 모으고, 각 경우에 대해 무엇이 그것을 검출하는지(컴파일러, 새니타이저, 라이브러리), 그리고 무엇은 아무도 검출하지 못하는지를 말해 준다.

학습 목표

  1. 대입, 증가 연산, 호출에서 식의 값과 그 부수 효과를 구별한다.
  2. 값으로 쓰인 x++ 식과 ++x 식의 결과를 예측하고, 복합 대입이 있는 프로그램을 손으로 따라간다.
  3. 표준이 정한 순서 없이 한 변수를 두 번 수정하거나, 읽으면서 수정하는 식을 알아보고, 이를 별개의 문으로 다시 쓴다.
  4. 어떤 구문을 정의된 동작, 구현 정의, 미지정, 정의되지 않은 동작 가운데 하나로 분류하고, 각 부류마다 예를 하나씩 든다.
  5. 목록에 있는 정의되지 않은 동작 하나하나에 대해, 컴파일러가 경고하는지, 새니타이저나 라이브러리가 멈추는지, 아니면 아무것도 검출하지 못하는지를 말한다.

값과 효과

산술 연산 수업은 식을 하나의 값으로 환원되는 트리로 공부했다. 2 + 3 * 4 식의 값은 14이고, 그것을 계산해도 식 자체 바깥의 아무것도 바뀌지 않는다. 관계와 논리 수업은 값이 true 값이나 false 값인 조건에 대해 같은 일을 했다. 그러나 값을 내는 것 말고도 프로그램의 상태에 흔적을 남기는 식이 있다. 변수의 값을 바꾸거나, 화면에 쓰거나, 입력에서 읽는 식이다. 이 흔적을 부수 효과, 또는 간단히 효과라고 하며, “부수”라는 말이 덜 중요하다는 뜻은 아니다. x = 7 식에서는 효과, 곧 x 변수의 값이 7이 되는 것이 바로 우리가 노리는 바이다.

그렇다면 모든 식은 두 가지 물음을 가지고 읽어야 한다. 무엇을 내는가, 그리고 무엇을 하는가. 세 가지 예가 이 구별을 굳혀 준다. count + 1 식은 count 변수 바로 뒤의 수를 내고 아무 일도 하지 않는다. count 변수는 제 값을 유지한다. count = 9 식은 9를 내고 count 변수의 값을 9로 만든다. std::println("안녕") 호출도 식이며, 함수 호출이고, 그에게서 중요한 것은 효과, 곧 한 줄을 쓰는 일이다. 직접 만드는 함수는 함수 수업에서 공부하는데, 거기서 이 구별이 다시 나타난다. 결과를 계산하기만 하는 함수는, 그에 더해 바깥의 무언가를 바꾸는 함수보다 이해하기도 쉽고 테스트하기도 쉽다.

count + 1; 같은 문은 올바른 C++이다. 값이 계산된 뒤 버려지므로 아무 쓸모가 없고, GCC는 -Wunused-value 옵션으로 이를 경고한다. 식문은 그 효과 때문에 의미가 있다. 따라서 효과가 있는 식이야말로 프로그램을 움직이는 식이고, 또한 조심해야 하는 식이다. 효과는 어느 시점에 일어나는데, 그 시점이 언제나 읽는 사람이 짐작하는 시점은 아니기 때문이다.