연산자
프로그램은 CPU가 처리하는 명령어의 묶음입니다. 일반적으로 연산자는 컴파일되면 명령어로 바뀌므로 연산자를 배우는 것은 결국 명령어를 익히는 겁니다.
산술 연산자
산술 연산자는 수학에서도 많이 사용하는 더하기, 빼기 등의 연산을 의미하며, 컴퓨터에서는 가장 중요한 명령어입니다. 산술 연산자에는 더하기(+), 빼기(-), 곱하기(*), 나누기(/), 나머지(%)가 있습니다. 모두 2개의 피연산자를 사용하며 이 중 빼기 연산자는 피연사자를 하나만 사용할 때 피연산자의 부호를 바꾸는 역할도 합니다.
int main(void)
{
int a, b;
int sum, sub, mul, div, rst, inv;
a = 10;
b = 20;
sum = a + b; // 30
sub = a - b; // -10
mul = a * b; // 200
div = b / a; // 2
rst = b % 3; // 2
inv = -a; // -10
return 0;
}
대입 연산자
대입 연산자(=)는 오른쪽 수식의 결과를 왼쪽 변수에 저장합니다. 여기서 수식은 상수나 변수 또는 연산자를 사용한 식을 모두 포함합니다.
증감 연산자
a라는 int형 변수에 1을 더하려면 a = a + 1; 이라고 쓰면 됩니다. 그런데 이렇게 쓰지 않고 ++a; 로 간단히 표현할 수도 있습니다. 이는 빼기에서도 마찬가지입니다. 증감 연산자(++, --)는 for문, while문, do while문 같은 반복문에서 루프마다 하나씩 더하거나 뺄 때 자주 사용됩니다.
전위 표기와 후위 표기
증가 연산자의 위치가 피연산자 앞에 놓이면 전위 표기(prefix), 뒤에 놓이면 후위 표기(postfix)라고 합니다. 전위 표기는 값이 증감하고 나서 연산에 사용하고, 후위 표기는 연산에 사용하고 나서 값이 증감합니다.
int main(void)
{
int a = 5, b = 5;
int pre, post;
pre = (++a) * 3; // pre = 18, a = 6
post = (b++) * 3; // post = 15, b = 6
return 0;
}
관계 연산자
특정한 기준, 조건에 관해 명령을 실행하려면 관계 연산자가 필요합니다. 관계 연산자에는 대소 관계 연산자와 동등 관계 연산자가 있습니다. 대소 관계는 < 또는 > 등의 기호를 사용하고, 동등 관계는 ==(같다)나 !=(같지 않다) 기호를 사용합니다.
이들 연산자는 모두 피연산자를 2개 사용하며, 연산의 결괏값은 1 또는 0 입니다. 컴파일러는 참과 거짓을 1(true)과 0(false)으로 판단하므로 관계식을 실행 조건 검사에 사용할 수 있습니다.
int main(void)
{
int a = 10, b = 20, c = 10;
int res;
res = (a > b); // 0
res = (a <= b); // 1
res = (a != b); // 1
res = (a == c); // 1
return 0;
}
C 언어는 기본적으로 boolean 자료형이 없으며, int 자료형으로 0을 false, 0이 아닌 값을 true로 판단합니다.
다만, C99부터는 stdbool.h를 포함하여 bool 자료형을 사용할 수 있습니다.
논리 연산자
논리 연산자는 논리 관계를 판단하는 데 사용하며 &&(AND), ||(OR), !(NOT) 3가지 뿐입니다.
&&는 논리곱 연산자로 2개의 피연산자가 모두 참일 때만 연산 결과가 참이 됩니다. ||는 논리합 연산자로 둘 중 하나라도 참이면 참이 됩니다. !는 논리부정 연산자이며 피연산자를 하나 사용해 그 참과 거짓을 바꿀 때 사용합니다.
int main(void)
{
int a = 30;
int res;
res = (a > 10) && (a < 20); // 0
res = (a < 10) || (a > 20); // 1
res = !(a >= 30); // 0
return 0;
}
그런데 논리 연산자를 사용할 때에는 주의할 점이 있습니다.

위 그림처럼 두 경우는 전혀 다른 결과를 도출하므로 수식을 만들 때 늘 유의해야 합니다.
숏 서킷 룰
&&와 ||는 숏 서킷 룰(short circuit rule)이 적용됩니다. 숏 서킷 룰이란 좌항만으로 &&와 || 연산 결과를 판별하는 기능입니다. 예를 들어 &&는 좌항이 거짓이면 우항과 관계없이 결과는 거짓이므로 우항을 살펴볼 필요가 없습니다.
또한 ||는 좌항이 참이면 우항과 관계없이 결과가 참입니다. 즉, && 연산 때 좌항이 거짓이거나, || 연산 때 좌항이 참이면 우항은 아예 실행되지 않습니다.
int a = 20, b = 20;
(a < 0) && (++b > 20)
만약 위와 같은 식에서 항상 ++b가 실행되길 기대한다면, 제대로 동작하지 않을 것입니다. 왜냐하면 a가 0보다 크면 b 값은 증가되지 않기 때문입니다. 이렇듯 숏 서킷 룰은 불필요한 연산을 줄여 실행 속도를 높일 수 있으나 예상치 못한 결과를 만들 수 있습니다.
+) 연산식의 처리 과정
컴파일러가 알아서 연산식을 필요한 명령어로 변환해 주니 크게 걱정할 필요는 없습니다만, 그 과정을 이해하면 버그를 줄이고 효율적인 프로그램을 작성할 수 있습니다. 일단 수식 sum = a + b 의 연산 과정을 그림으로 살펴보겠습니다.

연산을 하려면 메모리에 있는 a와 b의 값을 CPU의 저장 공간인 레지스터에 복사해야 합니다. 이 과정을 로드(load)라고하며 연산 명령 이전에 먼저 수행됩니다(1, 2번). 데이터가 레지스터에 저장되면 연산장치인 ALU에 의해 덧셈 연산이 수행되고 그 결괏값은 일단 레지스터에 저장됩니다(3번). 이후 대입 연산을 수행하면 메모리 공간인 sum에 복사되어 수식의 모든 과정이 완료됩니다(4번). 이 과정을 스토어(store)라고 합니다. 연산할 때는 메모리에 있는 변수의 값을 CPU로 복사해서 사용하므로 아무리 많은 연산을 수행해도 피연산자 a, b의 값은 변하지 않습니다. 반면 대입 연산을 수행한 sum은 연산장치 ALU에서 어떤 연산이 수행되느냐에 따라 값이 변할 수 있습니다.
형 변환 연산자
형 변환 연산자는 피연산자를 하나 가지며 피연산자의 값을 원하는 형태로 바꿉니다. 예를 들어 정수를 실수로, 실수를 정수로 바꿀 수 있습니다. 형 변환 연산자를 사용해서 피연산자의 형태를 바꿀 때는 피연산자의 값을 복사해 일시적으로 형태를 바꾸므로 연산 후 메모리에 남아 있는 피연산자의 형태나 값은 변하지 않습니다.
int main(void)
{
int a = 20, b = 3;
double res;
res = ((double)a) / ((double)b);
a = (int)res;
return 0;
}
자동 형 변환
컴퓨터는 데이터의 형태에 따라 다른 연산 방법을 사용하므로 피연산자가 2개 이상이라면 피연산자의 형태는 같아야 합니다. 따라서 컴파일러는 컴파일 과정에서 피연산자의 형태가 다르면 형태를 일치시키는 작업을 수행합니다. 이를 자동 형 변환(암묵적 형 변환, 묵시적 형 변환)이라고 합니다.
이런 형 변환의 기본 규칙은 데이터 크기가 작은 값이 크기가 큰 값으로 바뀌는 겁니다. 예를 들어 정수(4바이트)와 실수(8바이트)를 연산하면 정수가 실수로 자동 변환되어 연산됩니다. 다만, 대입 연산은 메모리에 값을 저장하므로 무조건 좌항의 변수형에 맞게 저장됩니다. 자동 형 변환은 형태가 다른 데이터를 자유롭게 연산할 수 있도록 도와주지만, 예상치 못한 값의 변형이 생길 수 있으니 가능하면 피연산자의 형태를 같게 맞춰 사용하는 편이 좋습니다.
sizeof 연산자
sizeof 연산자는 피연산자를 하나만 사용할 수 있으며 피연산자의 크기를 바이트 단위로 계산해서 알려 줍니다. 피연산자의 대상은 변수, 상수, 수식, 자료형 등이 될 수 있습니다.
이 연산자는 데이터의 크기를 확인하거나 메모리를 동적으로 할당하는 작업 등에 유용하게 사용됩니다.
#include <stdio.h>
int main(void)
{
int a = 10;
double b = 3.4;
printf("int형 변수의 크기 : %d\n", sizeof(a)); // 4
printf("double형 변수의 크기 : %d\n", sizeof(b)); // 8
printf("정수형 상수의 크기 : %d\n", sizeof(10)); // 4
printf("수식의 결괏값의 크기 : %d\n", sizeof(1.5 + 3.4)); // 8
printf("char형 자료형의 크기 : %d\n", sizeof(char)); // 1
return 0;
}
sizeof 연산자는 함수가 아니기 때문에 괄호를 사용하지 않고 sizeof a 와 같이 사용할 수 있으나, 편의상 피연산자에 괄호를 사용합니다.
복합대입 연산자
대입 연산자(=)와 증감 연산자(++,--)를 제외한 다른 연산자는 연산하고 나서 피연산자의 값을 바꾸지 않습니다. 만약에 연산 결과를 피연산자에 저장할 필요가 있다면 추가로 대입 연산을 수행해야 합니다. 이때 복합대입 연산자를 사용하면 간편합니다.
복합 대입 연산자는 연산 결과를 다시 피연산자에 저장합니다. 산술 복합대입 연산자로는 이렇게 +=, -=, *=, /=, %= 총 5가지가 있습니다.
int main(void)
{
int a = 10, b = 20;
int res = 2;
a += 20;
res *= b + 10;
return 0;
}
실행 결과는 a = 30, b = 20, res = 60 이 됩니다.
그중 res *= b + 10 수식이 연산되는 과정에 대해 그림으로 살펴보겠습니다.

위 그림과 같이 복합대입 연산자는 좌변의 값을 로드할 때 한 번, 결과를 저장할 때 또 한 사용됩니다.
콤마 연산자
콤마 연산자(,)는 한 번에 여러 개의 수식을 차례로 나열해야 할 때 사용합니다. 콤마 연산자는 왼쪽부터 오른쪽으로 차례로 연산을 수행하며 가장 오른쪽의 피연산자가 최종 결괏값이 됩니다.
int main(void)
{
int a = 10, b = 20;
int res;
res = (++a, ++b); // a: 11, b: 21, res: 21
return 0;
}
콤마 연산자는 대입 연산자보다 우선순위가 낮은 유일한 연산자입니다. 따라서 대입 연산자와 함께 사용할 때는 반드시 괄호가 필요합니다.
만약 res = ++a, ++b; 라고 작성했다면, 이는 res = ++a; ++b; 와 동일하게 처리됩니다. 혹시라도 별개의 두 문장을 한 줄에 작성한 거라면 콤마 연산자 대신 세미콜론을 사용하는 것이 좋습니다.
콤마 연산자는 제어문에서 조건실을 나열하는 괄호 안과 같이 세미콜론 사용이 불가능한 구조에서 사용합니다.
for (i = 0, j = 10; i < j; i++) 와 같은 반복문에서 초깃값을 설정하는 두 문장 (i = 0, j = 10) 을 세미콜론으로 구분하면 for 문의 구문에 맞지 않아 에러가 발생합니다.
조건 연산자
조건 연산자는 유일한 삼항 연산자로 ?와 : 기호를 함께 사용해 표현됩니다. 조건 연산자는 첫 번째 피연산자가 참이면 두 번째 피연산자가 결괏값이 되고, 첫 번째 피연산자가 거짓이면 세 번째 피연산자가 결괏값이 됩니다.
첫 번째 피연산자로는 주로 조건식이 사용되며 사용 방법은 다음과 같습니다.
int main(void)
{
int a = 10, b = 20, res;
res = (a > b) ? a : b;
return 0;
}
일반적으로 조건 연산자를 연산자라고 설명하지만 일치하는 명령어가 있는 것은 아닙니다. if ~ else문과 동일한 명령으로 번역되므로 연산자보다는 제어문으로 보는 것이 맞습니다.
비트 연산자
비트 연산자는 데이터를 비트 단위로 연산합니다. 비트 연산자에는 논리 연산을 수행하는 &, |, ^ 같은 비트 논리 연산자와 비트를 좌우로 움직이는 >>, << 같은 비트 이동 연산자가 있습니다. 비트 연산자는 데이터를 비트로 정확히 표현할 수 있는 정수에만 사용할 수 있습니다.
비트별 논리곱 연산자
& 연산은 두 비트가 모드 1인 경우에만 1로 계산합니다.

비트별 논리합 연산자
| 연산은 두 비트 중에서 하나라도 참이면 1로 계산합니다.

비트별 배타적 논리합 연산자
^ 연산은 두 비트가 서로 다른 경우에만 1로 계산합니다.

비트별 부정 연산자
~ 연산은 1을 0으로 바꾸고, 0을 1로 바꿉니다. 따라서 양수인 경우 부호 비트가 0에서 1로 바뀌므로 음수가 됩니다.

비트 이동 연산자
<< 은 비트를 왼쪽으로 이동시키고, >>은 오른쪽으로 이동시킵니다.
<< 의 경우 왼쪽으로 밀려나는 비트는 사라지고 오른쪽의 남는 비트는 0으로 채워집니다.

값을 왼쪽으로 한 비트씩 이동할 때마다 2가 곱해짐을 알 수 있습니다.
>> 의 경우 오른쪽으로 밀려나는 비트는 사라지고 왼쪽의 남는 비트는 0으로 채워집니다.
만약 a 값이 음수라면 왼쪽의 남는 비트는 모두 1로 채워집니다.

값을 오른쪽으로 한 비트씩 이동할 때마다 2로 나눈 몫이 되는 것을 알 수 있습니다.
비트 연산자는 대입 연산자와 결합해 사용할 수 있습니다. 예를 들어 a = a << 2; 와 같은 문장은 a <<= 2; 와 같이 쓸 수 있습니다. 결국 &=, ^=, |=, <<=, >>= 이렇게 5개의 복합대입 연산자를 사용할 수 있습니다.
연산 우선순위와 연산 방향
하나의 수식에서 2개 이상의 연산자가 함께 쓰일 때는 연산자의 우선순위에 따라 연산됩니다.
연산자 우선순위는 요약하면 다음과 같습니다:
- 단항 연산자 > 이항 연산자 > 삼항 연산자
- 산술 연산자 > (비트 이동 연산자) > 관계 연산자 > 논리 연산자
만약 우선 순위가 같은 경우에는 왼쪽부터 순서대로 연산됩니다.
단, 대입 연산자와 단항 연산자의 경우 오른쪽에서 왼쪽으로 연산이 수행됩니다.

연산자의 종류는 굉장히 많은데 그 많은 연산자의 연산 순서를 줄줄이 외우고 다니는 일은 쉽지 않습니다. 따라서 여러 연산자를 함께 사용할 때 의심되거나 분석이 까다롭디고 느껴진다면 주저 없이 괄호를 사용하는 것이 좋습니다.
그밖에 주소 연산자(&)와 간접 참조 연산자(*)는 포인터에 대해 학습할 때 살펴보고,
멤버 접근 연산자(.)와 간접 멤버 접근 연산자(->)는 구조체에 대해 학습할 때 살펴보겠습니다.
'Lang > C' 카테고리의 다른 글
| C (6) - 문자와 문자열 (2) | 2025.08.18 |
|---|---|
| C (5) - 배열과 포인터 ② (2) | 2025.08.15 |
| C (4) - 배열과 포인터 ① (3) | 2025.08.11 |
| C (3) - 제어문과 함수 (4) | 2025.08.07 |
| C (1) - 상수와 변수 (1) | 2025.07.08 |