C/C++ 삼항 연산자란?
C/C++에서 삼항 연산자(ternary operator)는 if~else 문을 한 줄로 대체할 수 있는 조건 연산자입니다. 기호는 ?:로 표현되며, ?는 if 부분에, :는 else 부분에 해당합니다.
형식은 다음과 같습니다.
조건식 ? exp2 : exp3
조건식이 참이면 exp2가, 거짓이면 exp3가 평가됩니다. 겉보기에는 단순해 보이지만, 삼항 표현식의 반환 타입이 어떻게 결정되는가라는 문제에서 의외로 흥미로운 결과들이 나타납니다. 아래 세 가지 예제 프로그램을 통해 살펴보겠습니다.
사례 1. 정상적으로 컴파일되는 경우 – 암시적 형 변환
다음 프로그램은 오류 없이 정상적으로 컴파일됩니다.
#include <iostream>
using namespace std;
int main(){
int test1 = 0;
float fvalue = 3.111f;
cout << (test1 ? fvalue : 0) << endl;
return 0;
}여기서 삼항 표현식의 반환 타입은 exp2의 타입인 float로 결정됩니다. exp3에 해당하는 리터럴 0은 int 타입이지만, int에서 float로의 암시적 형 변환(implicit conversion)이 가능하기 때문에 컴파일러가 자동으로 변환 코드를 삽입해 줍니다. 따라서 아무 문제 없이 컴파일되고 실행됩니다.
사례 2. 컴파일되지 않는 경우 – 공통 타입 부재
다음 프로그램은 컴파일되지 않습니다.
#include <iostream>
using namespace std;
int main(){
int test1 = 0;
cout << test1 ? "A String" : 0 << endl;
return 0;
}그 이유는 컴파일러가 삼항 표현식의 반환 타입을 결정하지 못하기 때문입니다. exp2는 문자열 리터럴(char 배열), exp3는 int인데, 이 두 타입 사이에는 서로 호환되는 공통 타입이 존재하지 않아 암시적 변환이 불가능합니다. 컴파일러는 두 피연산자를 하나의 타입으로 통일할 수 없으므로 컴파일 오류를 발생시킵니다.
참고로 위 코드에는 또 하나의 함정이 있습니다. 연산자 우선순위 때문에 <<가 ?:보다 먼저 묶이므로, 의도한 대로 동작하려면 삼항 표현식 전체를 괄호로 감싸야 합니다.
cout << (test1 ? "A String" : 0) << endl;
사례 3. 컴파일은 되지만 런타임에 실패하는 경우
다음 프로그램은 컴파일될 수 있지만, 실행 시점에 실패할 수 있습니다.
#include <iostream>
using namespace std;
int main(){
int test1 = 0;
cout << (test1 ? "A String" : 0) << endl;
return 0;
}이 경우 삼항 표현식의 반환 타입은 exp2에 맞추어 char*(문자열 포인터)로 결정됩니다. 그런데 실제로 거짓 조건에서 반환되는 값은 int 타입의 0입니다. 즉, 프로그램은 실행 시점에 0번지(널 포인터)에 저장된 문자열을 출력하려고 시도하게 됩니다. 잘못된 메모리 접근은 미정의 동작(undefined behavior)으로 이어져 세그멘테이션 폴트 같은 런타임 오류나 프로그램 강제 종료를 유발할 수 있습니다.
정리: 삼항 연산자의 타입 결정 규칙
세 가지 사례에서 다음과 같은 규칙을 확인할 수 있습니다.
- exp2가 결과 타입의 기준이 되며, exp3는 이 타입으로 변환되려고 시도합니다.
- 변환이 암시적으로 가능하면 컴파일러가 자동으로 변환 코드(stub)를 삽입합니다. (사례 1)
- 두 타입 간에 공통 타입을 찾을 수 없으면 컴파일러는 오류를 발생시킵니다. (사례 2)
- 컴파일러가 타입 불일치를 놓치면 프로그램은 런타임에 실패할 수 있습니다. (사례 3)
결론적으로 삼항 연산자는 단순히 if~else를 줄여 주는 문법 설탕처럼 보이지만, 내부적으로는 엄격한 타입 검사와 변환 규칙이 적용됩니다. 서로 다른 타입의 값을 삼항 연산자로 결합할 때는 반환 타입이 무엇으로 결정되는지, 암시적 변환이 안전한지 항상 신중하게 확인하는 것이 좋습니다.