Menu

C 언어 연산자: 산술, 논리, 그리고 우선순위

알아둘 가치가 있는 모든 C 연산자 - 산술, 대입, 비교, 논리, 증감 - 과 정수 나눗셈의 함정, 음수에서 나머지 연산이 하는 일, 그리고 믿고 쓸 수 있는 우선순위 표.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

연산자는 C의 동사입니다. 값을 받아 새 값을 만들어 냅니다. 40개쯤 있지만 열두어 개가 대부분의 코드를 덮고, 그중 둘은 따로 한 절을 둘 만큼 날카로운 함정을 가지고 있습니다.

산술 연산자

(형식 문자열의 %%는 퍼센트 기호 자체를 출력합니다. % 하나만 쓰면 형식 지정자가 시작됩니다.)

이항 산술 연산자는 + - * / % 다섯 개이고, 부호를 뒤집는 단항 -와 사실상 아무 일도 하지 않는 +가 있습니다.

정수 나눗셈의 함정

정수 둘에 대한 a / b정수 나눗셈을 수행하고 나머지를 버립니다. 반올림은 없습니다. 결과는 0 쪽으로 잘립니다.

마지막 경우가 운영 환경까지 살아남는 버그입니다. double에 대입해도 소용없습니다. 대입이 일어나기 전에 total / count 나눗셈은 이미 끝났고 이미 int이기 때문입니다. 해법은 식 안에서 한쪽 피연산자를 부동소수점으로 만드는 것이고, 보통 캐스팅을 씁니다.

관련된 규칙 둘. 정수를 0으로 나누는 것은 미정의 동작이고 보통 프로그램이 죽습니다. 그리고 INT_MIN / -1은 오버플로를 일으킵니다.

나머지 연산자

%는 나머지를 주고 정수에만 동작합니다.

짝수 판별에 x % 2 == 0을 쓰고, 인덱스를 배열 안으로 감쌀 때 % n을 쓰는 것이 일상적인 두 쓰임입니다.

음수에서 C의 %피제수의 부호를 따릅니다.

따라서 x가 음수일 수 있다면 x % 2 == 1은 홀수 판별로 망가진 검사입니다. -7 % 2-1입니다. 대신 x % 2 != 0을 쓰세요.

부동소수점 나머지에 %를 쓰면 컴파일 오류입니다. math.hfmod()를 쓰세요.

대입과 복합 대입

=는 값을 저장합니다. 복합 형태는 연산과 저장을 합칩니다.

x += 5x = x + 5를 뜻하지만 x를 한 번만 평가합니다. 대상이 arr[compute_index()] 같은 것일 때 이 차이가 중요합니다.

대입 자체도 대입된 값을 내놓는 식입니다. 그래서 a = b = c = 0이 동작하고(오른쪽에서 왼쪽으로 대입합니다), ===를 헷갈린 실수가 조용히 컴파일되는 것입니다.

증가와 감소

++는 1을 더하고 --는 1을 뺍니다. 각각 전위와 후위 형태가 있습니다.

단독 문장으로 쓰면 둘은 바꿔 쓸 수 있고, for 헤더에서 i++를 쓰는 것은 관습 말고는 깊은 이유가 없습니다.

절대 하면 안 되는 것은, 어떤 식 안에서 수정되고 있는 변수를 그 식에서 두 번 이상 쓰는 것입니다.

int i = 5;
int x = i++ + i++;      /* 미정의 동작 */
arr[i] = i++;           /* 미정의 동작 */
printf("%d %d", i++, i);/* 미정의 동작 */

C는 이 부분식들이 평가되는 순서를 정의하지 않으므로, 결과가 단순히 미지정인 것이 아니라 프로그램 전체의 동작이 미정의가 됩니다. 문장을 둘로 나누면 모호함이 사라집니다.

비교 연산자

여섯 개가 있고 모두 int를 만들어 냅니다. 참은 1, 거짓은 0입니다.

결과가 전용 불리언 타입이 아니라 평범한 int라는 점은 C를 규정하는 특징입니다. 거기서 따라 나오는 것들, 특히 === 버그는 C의 불리언을 보세요.

C에만 있는 비교 실수가 둘 있습니다. 연쇄 비교는 수학에서 기대하는 대로 동작하지 않습니다. if (1 < x < 10)은 항상 참인데, 1 < x가 0이나 1이 되고 그것이 10과 비교되기 때문입니다. if (x > 1 && x < 10)이라고 쓰세요. 그리고 문자열을 ==로 비교하면 글자가 아니라 포인터를 비교합니다. string.hstrcmp를 쓰세요.

논리 연산자

&&는 AND, ||는 OR, !는 NOT입니다. 셋 다 0이 아닌 값을 참으로 취급합니다.

이들은 단락 평가를 합니다. &&는 한쪽이 거짓이 되는 순간 멈추고, ||는 한쪽이 참이 되는 순간 멈춥니다. 나머지는 아예 평가되지 않으며, 이는 최적화에 그치지 않고 안전을 위해 기댈 수 있는 보장입니다.

두 검사의 순서를 바꾸면 NULL 호출이 널 포인터를 역참조하고 죽습니다. 먼저 지키고 나서 쓰세요. && 사슬의 순서는 논리의 일부입니다.

조건 연산자

C의 유일한 삼항 연산자는 두 값 중 하나를 고릅니다.

조건 ? 참일_때_값 : 거짓일_때_값입니다. 식이므로 if 문이 들어갈 수 없는 자리 - printf 인자 안, 초기화자 안 - 에도 들어갑니다. 짧게 유지하세요. 중첩된 조건 연산자는 금세 읽을 수 없게 됩니다.

비트 연산자, 짧게

이들은 정수의 개별 비트에 동작합니다.

플래그, 마스크, 하드웨어 레지스터에 쓰입니다. 주의할 점 둘. &|&&, ||아닙니다(단락 평가를 하지 않고 비트 단위로 동작합니다). 그리고 부호 있는 값을 시프트하거나 타입의 폭보다 많이 시프트하는 것은 미정의입니다. 비트 작업에는 부호 없는 타입을 쓰세요.

우선순위

괄호가 없는 식에서 무엇이 무엇에 묶이는지는 이 표가 결정합니다. 우선순위가 높은 것부터입니다.

단계연산자결합 방향
1() [] -> . x++ x--왼쪽에서 오른쪽
2! ~ ++x --x +x -x *p &x sizeof (type)오른쪽에서 왼쪽
3* / %왼쪽에서 오른쪽
4+ -왼쪽에서 오른쪽
5<< >>왼쪽에서 오른쪽
6< <= > >=왼쪽에서 오른쪽
7== !=왼쪽에서 오른쪽
8&왼쪽에서 오른쪽
9^왼쪽에서 오른쪽
10비트 OR왼쪽에서 오른쪽
11&&왼쪽에서 오른쪽
12논리 OR왼쪽에서 오른쪽
13?:오른쪽에서 왼쪽
14= += -= *= /= %=오른쪽에서 왼쪽
15,왼쪽에서 오른쪽

실질적인 결과는 이렇습니다.

a + b * c       /* a + (b * c)를 뜻한다 */
a < b == c < d  /* (a < b) == (c < d)를 뜻한다 - 의도한 경우는 드물다 */
x & 1 == 0      /* x & (1 == 0), 즉 x & 0을 뜻한다 - 고전적인 버그 */
*p++            /* *(p++)를 뜻한다 - p를 역참조하고, 그다음 전진시킨다 */

세 번째 줄은 외워 둘 만합니다. ==&보다 강하게 묶이므로 비트마스크 검사에는 괄호가 필요합니다. (x & 1) == 0처럼요.

열다섯 단계를 다 기억하는 사람은 없고, 그럴 필요도 없습니다. *+를 이기고, 비교가 &&를 이기고 &&||를 이긴다는 것만 익히고, 나머지는 전부 괄호를 치세요.

if ((flags & MASK) != 0 && (count > 0 || force)) { ... }

이 줄에는 없어도 되는 괄호가 있고, 그 덕분에 더 좋습니다.

자주 묻는 질문

C에서 %는 무슨 일을 하나요?

%는 나머지 연산자로, 정수 나눗셈의 나머지를 줍니다. 17 % 5는 2입니다. 정수에만 동작하며, floatdouble에 쓰면 컴파일 오류입니다. 부동소수점용으로는 math.hfmod()가 있습니다.

C에서 5 / 2가 왜 2인가요?

양쪽 피연산자가 모두 정수라서 C가 정수 나눗셈을 하고 소수 부분을 버리기 때문입니다. 2.5를 얻으려면 한쪽을 부동소수점으로 만드세요. 5 / 2.0처럼 쓰거나 (double)a / b로 한쪽을 캐스팅하면 됩니다.

i++와 ++i의 차이는 무엇인가요?

둘 다 i에 1을 더합니다. ++i(전위)는 먼저 증가시키고 새 값을 내놓고, i++(후위)는 이전 값을 내놓은 뒤 증가시킵니다. 문장 하나로 단독으로 쓰면 둘은 같습니다. 차이는 int b = a++;int b = ++a;처럼 결과를 쓸 때만 의미가 있습니다.

C의 연산자 우선순위란 무엇인가요?

괄호가 없는 식에서 연산자가 결합되는 순서입니다. *, /, %+, -보다 강하게 묶이고, 그다음이 비교, 그다음 &&, ||, 마지막이 대입입니다. 헷갈리면 괄호를 넣으세요. 비용이 들지 않고 의문이 사라집니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기