비교 연산자
비교 연산자 (Comparison operators)
비교 연산자는 조건을 검사해서, 그 조건이 논리적으로 참이면 1을, 거짓이면 0을 반환하는 이항 연산자예요. < 같은 관계 연산자와 == 같은 같음 연산자로 나뉘고, 포인터 비교에는 또 다른 규칙이 숨어 있어요. NaN을 비교하면 왜 항상 거짓인지도 여기서 확인할 수 있어요.
본문
비교 연산자는 조건을 검사해서, 그 조건이 논리적으로 참이면 1을, 거짓이면 0을 반환하는 이항 연산자예요.
| 연산자 | 연산자 이름 | 예 | 설명 |
|---|---|---|---|
== |
같음 | a==b |
a가 b와 같음 |
!= |
같지 않음 | a!=b |
a가 b와 같지 않음 |
< |
작음 | a<b |
a가 b보다 작음 |
> |
큼 | a>b |
a가 b보다 큼 |
<= |
작거나 같음 | a<=b |
a가 b보다 작거나 같음 |
>= |
크거나 같음 | a>=b |
a가 b보다 크거나 같음 |
관계 연산자 (Relational operators)
관계 연산자 표현식의 형태는 다음과 같아요.
| 형태 | (번호) |
|---|---|
lhs < rhs |
(1) |
lhs > rhs |
(2) |
lhs <= rhs |
(3) |
lhs >= rhs |
(4) |
- 작음 표현식
- 큼 표현식
- 작거나 같음 표현식
- 크거나 같음 표현식
여기서
| lhs, rhs | - | 둘 다 실수 타입이거나 둘 다 객체에 대한 포인터 타입인 표현식 |
|---|
어떤 관계 연산자 표현식의 타입도 int이고, 그 값(lvalue가 아닌)은 지정된 관계가 성립하면 1, 성립하지 않으면 0이에요.
lhs와 rhs가 어떤 실수 타입의 표현식이면,
- 통상 산술 변환이 수행되고
- 변환 후의 피연산자 값이 일반적인 수학적 의미로 비교돼요 (단, 양의 0과 음의 0은 같게 비교되고, NaN 값이 관련된 어떤 비교도 0을 반환해요)
주의: 복소수와 허수는 이 연산자로 비교할 수 없어요.
lhs와 rhs가 포인터 타입의 표현식이면, 둘 다 (가리켜지는 객체의 한정자는 무시하고) 호환되는 타입의 객체에 대한 포인터여야 해요.
- 어떤 배열의 요소도 아닌 객체에 대한 포인터는 요소가 하나인 배열의 요소를 가리키는 것처럼 취급돼요
- 두 포인터가 같은 객체를 가리키거나, 둘 다 같은 배열의 끝 바로 다음을 가리키면 같게 비교돼요
- 두 포인터가 같은 배열의 서로 다른 요소를 가리키면, 더 큰 인덱스의 요소를 가리키는 쪽이 크게 비교돼요
- 한 포인터가 배열의 요소를 가리키고 다른 포인터가 같은 배열의 끝 바로 다음을 가리키면, 끝 바로 다음을 가리키는 쪽이 크게 비교돼요
- 두 포인터가 같은
struct의 멤버를 가리키면, struct 정의에서 더 나중에 선언된 멤버를 가리키는 포인터가 더 먼저 선언된 멤버를 가리키는 포인터보다 크게 비교돼요 - 같은 union의 멤버를 가리키는 포인터들은 같게 비교돼요
- 그 밖의 모든 포인터 비교는 정의되지 않은 동작을 일으켜요
#include <assert.h>
int main(void)
{
assert(1 < 2);
assert(2 + 2 <= 4.0); // int converts to double, two 4.0's compare equal
struct { int x, y; } s;
assert(&s.x < &s.y); // struct members compare in order of declaration
double d = 0.0/0.0; // NaN
assert( !(d < d) );
assert( !(d > d) );
assert( !(d <= d) );
assert( !(d >= d) );
assert( !(d == d) );
float f = 0.1; // f = 0.100000001490116119384765625
double g = 0.1; // g = 0.1000000000000000055511151231257827021181583404541015625
assert(f > g); // different values
}
같음 연산자 (Equality operators)
같음 연산자 표현식의 형태는 다음과 같아요.
| 형태 | (번호) |
|---|---|
lhs == rhs |
(1) |
lhs != rhs |
(2) |
- 같음 표현식
- 같지 않음 표현식
여기서
| lhs, rhs | - | 다음 중 하나를 만족하는 표현식: |
|---|---|---|
| - | 둘 다 어떤 산술 타입(복소수·허수 포함) | |
| - | 둘 다 nullptr_t 타입 (C23부터) |
|
| - | 하나가 nullptr_t 타입이고 다른 하나가 널 포인터 상수 (C23부터) |
|
| - | 둘 다 (가리켜지는 타입의 한정자를 무시하고) 호환되는 타입의 객체나 함수에 대한 포인터 | |
| - | 하나는 객체에 대한 포인터이고 다른 하나는 (한정될 수 있는) void에 대한 포인터 |
|
| - | 하나는 객체나 함수에 대한 포인터이고 다른 하나는 NULL 또는 nullptr(C23부터) 같은 널 포인터 상수 |
어떤 같음 연산자 표현식의 타입도 int이고, 그 값(lvalue가 아닌)은 지정된 관계가 성립하면 1, 성립하지 않으면 0이에요.
-
두 피연산자가 모두 산술 타입이면 통상 산술 변환이 수행되고, 결과 값이 일반적인 수학적 의미로 비교돼요 (단, 양의 0과 음의 0은 같게 비교되고, 자기 자신과의 같음 비교를 포함해 NaN 값이 관련된 어떤 비교도 0을 반환해요). 특히 복소수 타입의 값은 실수 부분이 같게 비교되고 허수 부분이 같게 비교되면 같아요.
-
두 개의
nullptr_t값, 또는 하나의nullptr_t값과 널 포인터 상수는 같게 비교돼요. (C23부터) -
한 피연산자가 포인터이고 다른 하나가 널 포인터 상수이면, 널 포인터 상수는 먼저 포인터의 타입으로 변환되고(널 포인터 값을 얻어요), 두 포인터는 아래에서 설명한 대로 비교돼요
-
한 피연산자가 포인터이고 다른 하나가
void에 대한 포인터이면, void가 아닌 포인터가void에 대한 포인터로 변환되고 두 포인터는 아래에서 설명한 대로 비교돼요 -
두 포인터는 다음 중 하나라도 참이면 같게 비교돼요:
- 둘 다 자기 타입의 널 포인터 값인 경우
- 둘 다 같은 객체나 함수를 가리키는 경우
- 하나가 struct/union/배열 객체를 가리키고 다른 하나가 그 첫 멤버/아무 멤버/첫 요소를 가리키는 경우
- 둘 다 같은 배열의 마지막 요소 바로 다음을 가리키는 경우
- 하나는 배열의 끝 바로 다음을 가리키고, 다른 하나는 더 큰 배열이나 패딩이 없는 struct 안에서 그 배열 바로 뒤에 오는 다른 (같은 타입의) 배열의 시작을 가리키는 경우
(관계 연산자와 마찬가지로, 어떤 배열의 요소도 아닌 객체에 대한 포인터는 크기 1의 배열의 요소에 대한 포인터처럼 동작해요)
참고 (Notes)
struct 타입의 객체는 자동으로 같다고 비교되지 않아요. 또 memcmp로 비교하는 것도 신뢰할 수 없어요. 패딩 바이트가 어떤 값을 가질지 모르기 때문이에요.
포인터 비교가 void에 대한 포인터와 함께 동작하므로, 매크로 NULL은 C에서 (void*)0으로 정의될 수 있어요. 단 C++에서는 void 포인터가 타입 있는 포인터로 암시적으로 변환되지 않으므로 그렇게 정의하면 무효예요.
부동소수점 값을 같음 비교할 때는 주의해야 해요. 많은 연산의 결과가 정확히 표현될 수 없고 반올림돼야 하기 때문이에요. 실제로는 부동소수점 수를 비교할 때 마지막 자리의 한 단위(units in the last place) 하나 이상의 차이를 허용하는 방식으로 비교하는 게 보통이에요.
#include <assert.h>
int main(void)
{
assert(2 + 2 == 4.0); // int converts to double, two 4.0's compare equal
int n[2][3] = {1,2,3,4,5,6};
int *p1 = &n[0][2]; // last element in the first row
int *p2 = &n[1][0]; // start of second row
assert(p1 + 1 == p2); // compare equal
double d = 0.0/0.0; // NaN
assert(d != d); // NaN does not equal itself
float f = 0.1; // f = 0.100000001490116119384765625
double g = 0.1; // g = 0.1000000000000000055511151231257827021181583404541015625
assert(f != g); // different values
}
더 알아보기
- 비교 결과는 보통 조건문·논리 연산자와 함께 흐름 제어에 쓰여요.
- 포인터 비교 규칙은 배열·포인터 산술 문서와 이어져요.
- cppreference의 Comparison operators 원문에서 표준 절 번호를 더 확인할 수 있어요.