이 글은 작성자가 K.N.King C Programming: A Modern Approach Second Eddtion을 보고 직접 공부한 뒤에 요약한 것입니다.
단항연산자 : +
이항연산자 : + - * / %
negative operand에 / %를 사용하는 것은 Implementation-Defined Behavior이다.
내부 구현 방식에 따라 결과가 달라지는 행동을 Implementation-Defined Behavior라고 한다.
C89와 C99에서는 (a / b) * b + a % b의 값이 언제나 a와 같도록 한다. 그래서 negative operand의 % 연산의 값이 달라진다.
C89에서는 구현(implementation)에 따라 부호의 결과가 다르다.(-9 / 7은 -1도 되고 -2도 될 수 있음. 소수점을 내림을 할 것인지 올림을 할 것인지)
C99에서는 / 는 부호와 상관없이 0과 가까운 쪽이 결과가 되고(-9 / 7 = -1), %는 i % j의 꼴일 때 i의 부호에 따른다.
수학 연산자의 우선순위
Highest : + - (단항)
* / %
Lowest : + - (이항)
i + j * k 는 i + (j * k) 와 동일
-i * -j 는 (-i) * (-j) 와 동일
+i +j / k 는 (+j) + (j / k)와 동일
우선순위가 같을 때 결합(associativity)이 작동한다.
만약 왼쪽부터 오른쪽으로 그룹화시키는 연산자라면 left associative 연산자라고 부른다.
이항 수학 연산자(*, /, %, +, -)는 모두 left associative이다.
i - j - k 는 (i - j) - k 와 동일
i * j / k 는 (i * j) / k 와 동일
만약 오른쪽부터 왼쪽으로 그룹화시키는 연산자라면 right associative 연산자라고 부른다.
단항 수학 연산자(+, -)는 둘다 right associative이다.
- + i 는 -(+i) 와 동일
v = e
표현식 e를 계산한 후 v에 복사한다.
C에서 대입은 연산자이고, 두 개의 숫자를 더하는 것이 결과를 만들어내듯 대입은 결과를 만들어낸다.
v = e의 값은 대입 이후의 v의 값이다.
대부분 C언어 연산자들은 피연산자들을 수정하지 않지만, 몇몇 연산자는 피연산자들을 수정한다.
단순 계산보다 그 이상을 하는 연산자들은 Side Effect를 가진다고 한다.
단순 대입 연산자인 =는 왼쪽의 피연산자를 수정하는 side effect를 가진 연산자이다.
표현식 i = 0은 0의 결과를 내고, side effect로써 0의 값을 i에 대입한다.
i = j = k = 0;
i = (j = (k = 0));
= 연산자는 right associative이고, 위와 같이 연계해서도 사용가능하다.
대입 연산자는 왼쪽에 있는 피연산자로 lvalue를 필요로 한다. lvalue는 컴퓨터 메모리안에 저장되는 것으로, 계산의 결과나 상수를 의미하는 것이 아니다. 변수들은 lvalue이다. 10, 2*i 와 같은 표현식은 lvalue가 아니다.
C언어는 복합 대입(compound assignment) 연산자를 제공한다.
i += 2; // i = i + 2와결과가 같은 복합 대입 연산
v += e와 v = v + e는 같지 않다.
위의 사실에 유념해야 한다. 하나의 문제는 연산자 우선순위이다.
i *= j + k 는 i = i * j + k와 같지 않다.
v += e가 v = v + e와 다른 이유는 v가 스스로 side effect를 가지기 때문이다.
이에 대한 건 후에 설명하겠다.
복합 대입 연산자는 = 연산자와 같은 특성을 가진다. 특히 이 연산자는 right associative이다.
C언어에는 1을 더하는 ++ 증가연산자와 1을 빼는 -- 감소연산자가 있다.
전위(prefix) 연산자나 후위(postfix) 연산자로 쓰일 수 있다.
피연산자의 값을 수정하는 side effect를 가지고 있다.
++와 --는 단항 연산자인 +와 -보다 높은 우선순위를 가졌고, right associative이다.
i = 1;
printf("%d \n", ++i);
printf("%d \n", i);
Output
2
2
i = 1;
printf("%d \n", i++);
printf("%d \n", i);
Output
1
2
우선순위 이름 Symbols Associativity
1 increment(postfix) ++ left
decrement(postfix) -- left
2 increment(prefix) ++ right
decrement(prefix) -- right
unary plus + right
unary minus + right
3 multiplicative * / % left
4 additive + - left
5 assignment = *= /= %= += -= right
예시
a = b += c++ - d + --e / -f
a = b += (c++) - d + --e / -f
a = b += (c++) - d + (--e) / (-f)
a = b += (c++) - d + ((--e) / (-f))
a = b += (((c++) - d) + ((--e) / (-f)))
a = (b += (((c++) - d) + ((--e) / (-f))))
(a = b += (((c++) - d) + ((--e) / (-f)))))
C언어는 하위 표현식의 계산 순서를 정의하지 않는다. 하위 표현식의 값은 보통 계산 순서에 상관없을 때가 많지만, 하위표현식이 피연산자 중 하나를 수정했을 때에는 그렇지 않다.
a = 5;
c = (b = a + 2) - (a = 1);
두번째 구문은 정의되지 않았다(Undefined). 이런 정의되지 않은 행동을 Undefined Behavior라고 한다. C언어 표준은 이것이 어떤 일을 발생시킬 지 말해주지 않는다.
i = 2;
j = i * i++;
j를 출력하면 몇일까? 4가 나올까?
알 수없다. subexpression의 계산 순서에 따라 6이 될 수도 있다.
C언어는 어떤 표현식이라도 구문(statement)으로 사용될 수 있다.
++i;
++i는 큰 표현식의 일부가 아니고, 구문으로 사용되었기 때문에 결과값은 버려진다. 그리고 다음 구문이 실행된다.
i = 1; //i에 1을 대입함
i--; // i가 감소함
i * j - 1; // i, j의 값이 변하지 않았기 때문에 아무 effect가 없고, 어떠한 의미도 없음
lvalue가 있으면 rvalue도 있지 않을까? rvalue도 존재한다. 대입 연산자의 왼쪽이 lvalue라면 오른쪽이 rvalue이다. rvalue는 변수, 상수, 또는 더 복잡한 표현식이 될 수 있다. C표준에서는 rvalue 대신에 expression이라는 용어를 사용한다.
v가 side effect를 가질 때, v += e와 v = v + e는 같지 않다.
v += e는 딱 한 번만 v가 계산된다. 하지만 v = v + e는 v가 두 번 계산된다. 그러므로 v의 계산에 의한 side effect도 후자의 경우 두 번 발생할 것이다.
a[i++] += 2;
a[i++] = a[i++] + 2;
전자의 구문은 어떠한 문제도 없고 i가 한번 증가할 것이다.
후자의 구문은 undefined behavior이다. 어떠한 동작을 할 지 예측할 수 없기 때문이다.
후위 연산자 ++와 --는 언제 작동할까?
C 표준에서 도입한 개념인 sequence point와 관련이 있는데, 피연산자의 저장된 값을 수정하는 것은 이전 sequence point와 다음 sequence point 사이에서 일어날 것이다.
sequence point는 나중에 다루겠다.