5-1-마.복잡한 수식

증감 연산은 아주 흔한 연산이며 CPU도 기계어 차원의 증감 명령을 별도로 가지고 있다. 어떤 값에 1을 더해 다시 대입하는 a=a+1(어셈블리로 ADD [a],1)보다 값을 1증가시키는 a++(어셈블리로 INC [a])이 속도도 더 빠르고 메모리도 적게 차지한다. 그러나 C언어 차원에서 a=a+1과 a++은 별 차이가 없는데 왜냐하면 컴파일러가 a=a+1을 a++로 바꿔서 컴파일하기 때문이다. 컴파일러는 생각보다 훨씬 더 똑똑하다.

증감 연산자는 다른 언어에는 없는 C언어의 아주 독특한 연산자이며 사용해 보면 아주 편리하다. C언어의 다음 버전을 C++이라고 부르는데 이 이름에 사용된 ++이 바로 증가 연산자를 의미한다. 그만큼 ++은 C언어의 큰 장점이고 또한 C를 다른 언어와 차별화하는 상징적인 연산자인 것이다.

증감 연산자가 비록 아주 편리하고 좋기는 하지만 너무 남발하게 되면 부작용이 생길 수도 있다. 주로 복잡한 수식내에서 증감 연산자를 사용할 때 문제가 발생하는데 어떤 문제점들이 있는지 보자.

 

: inconeline

#include <Turboc.h>

 

void main()

{

     int i=3;

 

     printf("%d, %d\n",i,++i);

}

 

3의 값을 가지는 i를 두 번 출력하되 한 번은 그냥 i값을, 한 번은 전위형으로 i를 1증가시켜서 출력했다. 이 코드의 실행 결과에 대해 아마 대부분 3, 4를 예상하겠지만 실제 결과는 4, 4로 출력된다. i가 먼저 출력되고 다음으로 i가 1증가한 값이 출력되는 것이 정상적일 것 같지만 결과는 거꾸로이다. 왜냐하면 C는 함수의 인수를 뒤에서부터 순서대로 전달하기 때문이다. 즉, ++i가 먼저 실행되어 그 결과인 4가 전달되고 다음으로 이미 4가 되어 버린 i를 한 번 더 전달한다.

C표준에 인수 전달 순서는 지정되어 있지 않으며(Unspecified) 대부분의 컴파일러는 뒤쪽 인수부터 평가하는데 왜 인수를 뒤에서부터 평가하는가하면 가변 인수를 다루기가 편하기 때문이다. 이 문제는 호출 규약(Calling Convention)과 관련된 문제이며 다음에 상세히 배우게 되겠지만 무척 복잡하다. 컴파일러마다 평가 순서가 달라질 수 있으므로 이런 수식에서 ++ 연산자를 사용하면 이식성에도 불리하다. 이런 복잡한 문제를 신경쓰고 싶지 않으면 수식내에서는 증감 연산자를 쓰지 않으면 된다. 만약 i와 i를 1증가시킨 후의 값을 출력하도록 하고 싶다면 다음과 같이 쓰는 것이 더 좋다.

 

int i=3;

printf("%d, ",i);

++i;                                 // 또는 i++;

printf("%d\n",i);

 

이렇게 하면 원하는대로 결과 3, 4가 나올 것이다. 코드가 조금 길어지기는 했지만 얼마나 보기좋고 읽기에 편한가? 별 생각없이 읽기만 해도 결과를 바로 예측할 수 있다. 다음 수식을 보자.

 

b=(++a+1)*a++;

b=a++ + ++a;

 

두 식 모두 문법적으로는 아무 문제가 없는 합법적인 문장이며 엄격하게 따져 보면 결과를 예측할 수도 있다. 그러나 보다시피 수식이 뭔가 불안해 보이고 어떤 의도로 작성한 코드인지 한눈에 알아보기 어렵다. 더구나 이런 수식에 대한 평가 방법은 컴파일러마다 달라서 이식성에도 좋지 않다. 이런 현란한 코드는 절대로 쓰지 말아야 한다.

현란하고 복잡한 코드가 많다고 해서 실력이 좋은 프로그래머라고 할 수 없다. 오히려 반대로 호기심에 가득찬 철부지 초보일수록 현란한 코드를 즐겨씀으로써 부족한 실력을 감추려고 애쓰는 편이다. 남의 소스를 보다가 저런 코드가 보이면 "이 녀석 초짜로구만"이라고 생각하면 거의 틀림없다. 경험있는 개발자는 결코 어려운 코드를 쓰지 않는다.

 

 Over1000

1부터 계속 증가되는 정수의 합을 누적시키다가 최초로 1000을 넘는 수를 구해 출력하라. 즉 1부터 어디까지 누적해야 1000이 넘는지를 구하는 문제이다.

 

참고 : Undefined, Unspecified

C 스팩 문서나 문법 서적을 보면 정의되지 않는다는 뜻의 Undefined라는 용어가 종종 등장한다. 이 용어는 어떻게 될 지 알 수 없으며 결과를 책임질 수 없다는 뜻이다. 이런 예 중 하나는 printf의 서식과 인수의 타입이 일치하지 않을 때인데 스팩은 이런 비정상적인 상황에 대해 일일이 어떻게 처리해야 한다고 명시하지 않는다. 따라서 컴파일러 제작사들은 정의되지 않은 동작에 대해서 굳이 예외 처리를 할 필요가 없으며 개발자들은 이런 문법에 대해서는 주의를 기울일 필요가 있다. 정의되지 않은 동작은 운에 따라 결과가 달라지므로 개발할 때나 테스트할 때는 잘 실행되더라도 막상 최종 사용자가 프로그램을 쓸 때 다운될 수도 있다.

이에 비해 Unspecified라는 용어는 가능한 몇 가지 방법 중 어떤 것을 사용하라는 강제가 없다는 뜻이며 지정되지 않았다고 번역한다. 컴파일러 제작사들은 자신들의 편의와 효율성에 따라 원하는대로 정책을 선택하여 지정되지 않은 기능을 구현한다. 컴파일러에 따라 결과가 달라질 수 있고 이식성이 없으므로 가급적이면 지정되지 않은 기능에 의존하지 말아야 한다. 현재는 잘 실행되는데 컴파일러가 바뀌면 엉뚱하게 동작한다면 이는 코드를 만든 사람이 책임져야 한다.