5-4.연산 규칙

5-4-가.연산 순위

연산 순위란 수식내에 여러 가지 연산자가 있을 경우 어떤 연산을 먼저 처리하는가를 지정하는 것이다. a가 4라고 했을 때 다음 수식의 결과 a에 어떤 값이 대입될까?

 

a=a*2+3;

a=a+2*3;

 

첫 번째 식은 11로 계산되며 두 번째 식은 10으로 계산될 것이다. 가끔 두 번째 식의 결과가 18이라고 착각하는 사람이 있다. 덧셈보다 곱셈이 우선순위가 높다는 것을 잠시 깜박하면 전혀 다른 결과가 나오기도 한다. 수학에서 연산의 우선 순위를 정하듯이 C도 모든 연산자에 대해 연산 우선 순위를 미리 정해 놓았다. 다음 도표에 C의 연산 우선 순위와 다음 항에서 알아볼 결합 순서에 대해 정리해 두었다.

 

순위

연산자

결합순서

1

( ) [ ] -> .

왼쪽 우선

2

! ~ ++ -- + -(부호) *(포인터) & sizeof 캐스트

오른쪽 우선

3

*(곱셈) / %

왼쪽 우선

4

+ -(덧셈, 뺄셈)

왼쪽 우선

5

<< >>

왼쪽 우선

6

< <= > >=

왼쪽 우선

7

== !=

왼쪽 우선

8

&

왼쪽 우선

9

^

왼쪽 우선

10

|

왼쪽 우선

11

&&

왼쪽 우선

12

||

왼쪽 우선

13

? :

오른쪽 우선

14

= 복합대입

오른쪽 우선

15

,

왼쪽 우선

 

1순위에서 15순위까지 순서가 매겨져 있으며 순위가 빠른 연산자가 가장 먼저 실행된다. 순위표에서 보다시피 덧셈보다 곱셈의 우선 순위가 높고 곱셈보다 증감 연산자의 우선 순위가 더 높다. 연산자의 우선 순위는 대체로 상식과 일치하므로 도표만 한 번 주의 깊게 봐 두면 큰 문제가 없다. 그러나 C의 연산자 중 일부는 실생활에 사용하는 것도 아니고 익숙치 않기 때문에 가끔 연산 순위로 인해 고생을 할 경우가 있다.

앞서 예를 든 다음 수식을 조금 변형하여 a=a*2+3을 연산해 보자. a가 4라고 했을 때 a=a*2+3 수식은 곱셈이 먼저 계산된 후 덧셈이 계산되므로 결과는 11이 된다. 이제 연산자에 대해서도 다 배웠다싶어 이 식을 복합 대입 연산자로 바꾸었다고 하자.

 

a*=2+3;

 

이렇게 고치면 된다. 그러나 결과는 전혀 딴판인 20이 된다. 연산 순위표를 보면 곱셈은 덧셈보다 순위가 높지만 대입 연산자나 복합 대입 연산자는 덧셈보다 순위가 더 낮다. 그래서 a에 2를 먼저 곱하고 3이 더해지는 것이 아니라 2와 3이 먼저 더해지고 이 값과 a가 곱해져서 다시 a에 대입된다. 결국 4*5=20이 되고 만다. 예상치 못한 부작용이 있기 때문에 복합 대입 연산자는 복잡한 수식에서는 사용하지 말라고 하는 것이다.

이런 짧은 코드에서는 결과를 보고 바로 어디가 잘못되었는지 찾을 수 있다. 그러나 아주 긴 프로그램에서 이런 실수를 하면 틀린 연산의 효과가 곧바로 나타나는 것이 아니기 때문에 찾기가 무척이나 어렵다. 연산 우선 순위는 보기보다 까다로와서 항상 주의해야 한다. 우선 순위표를 책상앞에 붙여 놓든가 아니면 조금이라도 의심이 가는 식은 괄호를 분명히 싸 두는 것이 좋다.

앞서 관계 연산문의 결과를 변수에 대입하는 a=(b==c);라는 연산문을 소개한 적이 있는데 대입 연산보다 관계 연산문이 우선 순위가 높으므로 a=b==c;라고 쓸 수도 있다. 이 식은 문법적인 문제가 없고 잘 동작하기는 하지만 보다시피 왠지 불안해 보이는데 시각적인 안정감을 위해 괄호를 싸는 것이 더 좋다. 다음 코드를 보자.

 

if (ch=getch() == 'x')

 

문자 하나를 입력받은 후 그 값이 'x'이면 이라는 조건문인데 이 조건식은 판단은 제대로 하지만 ch에는 항상 0 아니면 1이 대입된다. 왜냐하면 ==이 =보다 우선 순위가 높아서 getch()와 'x'가 먼저 비교되고 비교 결과가 ch에 대입되기 때문인데 입력을 먼저 받은 후 입력받은 값을 비교하려면 if ((ch=getch()) == 'x') 이렇게 써야 옳다. 다음 조건문의 평가 결과는 무엇인지 예측해 보자.

 

int a=1,b=-1,c=-1,d=-1;

if (a==1 || b==1  &&  c==1 || d==1) {

     puts("참이다");

} else {

     puts("거짓이다");

}

 

네 개의 조건이 논리 연산자로 연결되어 있는데 &&를 기준으로 왼쪽, 오른쪽이 모두 참이어야 전체가 참이 될 것 같다. 사람의 직관력은 이 식을 ((a==1 || b==1) && (c==1 || d==1)) 이렇게 평가한다. 그런데 &&의 우변을 구성하는 두 조건이 모두 거짓이므로 전체식은 거짓이 될 것 같아 보인다. 그러나 실행해 보면 &&가 우선 순위가 높아 (a==1 || (b==1 && c==1) || d==1)로 평가되므로 첫 번째 조건만 참이면 전체가 참이 된다. 괄호가 없어 결과를 예측하기 쉽지 않은데 논리 연산자를 두 개 이상 쓸 경우 괄호로 우선 순위를 분명히 표시하는 것이 좋다.

다음은 나의 실수담이다. 어떤 변수의 두 번째 비트가 1인지 점검하기 위해 다음 조건문을 사용했다.

 

if (a & 2 != 0)

 

두 번째 비트는 이진수로 10이므로 십진수 2와 마스크 오프시킨 후 이 값이 0인가 아닌가를 보면 된다. 그러나 이 식은 제대로 동작하지 않는데 a와 2를 &한 후 0과 비교하는 것이 아니라 2가 0이 아닌지 보고 그 결과를 a와 &연산한다. 우선 순위표에 & 연산보다 관계 연산자가 순위가 더 높기 때문이다. 정확하게 조건을 판단하려면 괄호가 필요하다.

 

if ((a & 2) != 0)

 

이렇게 해야 a와 2를 & 연산한 후 그 결과가 0이 아닌지 점검한다. 아니면 차라리 if (a & 2)라고만 해도 된다. 비트 연산자와 관계 연산자는 많이 헷갈리는데 데니스 리치의 The C Programming Language에도 이 두 연산자의 우선 순위를 특히 조심하라고 강조되어 있다.