연산자
SafeC는 C의 모든 연산자에 더해 오버플로 제어를 위한 SafeC 고유 확장을 지원합니다. 이 페이지는 모든 연산자와 그 우선순위, 결합 방향에 대한 완전한 레퍼런스입니다.
산술 연산자
이항 산술
| 연산자 | 설명 | 예시 |
|---|---|---|
+ |
덧셈 | a + b |
- |
뺄셈 | a - b |
* |
곱셈 | a * b |
/ |
나눗셈 | a / b |
% |
나머지 (모듈로) | a % b |
단항 산술
| 연산자 | 설명 | 예시 |
|---|---|---|
+x |
단항 플러스 (항등) | +a |
-x |
단항 부정 | -a |
증가와 감소
| 연산자 | 설명 | 예시 |
|---|---|---|
++x |
전위 증가 (증가 후 반환) | ++counter |
x++ |
후위 증가 (반환 후 증가) | counter++ |
--x |
전위 감소 (감소 후 반환) | --index |
x-- |
후위 감소 (반환 후 감소) | index-- |
int x = 5;
int a = ++x; // x는 6, a는 6
int b = x++; // b는 6, x는 7
int c = --x; // x는 6, c는 6
int d = x--; // d는 6, x는 5
오버플로 인지 산술
SafeC는 명시적인 오버플로 제어를 위한 연산자 변형을 추가합니다. 자세한 사용법은 오버플로 연산자를 참고하십시오.
| 연산자 | 설명 | 동작 |
|---|---|---|
+| |
래핑 덧셈 | 2의 보수 랩어라운드 |
-| |
래핑 뺄셈 | 2의 보수 랩어라운드 |
*| |
래핑 곱셈 | 2의 보수 랩어라운드 |
+% |
포화 덧셈 | 최소/최대값으로 클램프 |
-% |
포화 뺄셈 | 최소/최대값으로 클램프 |
*% |
포화 곱셈 | 최소/최대값으로 클램프 |
대입 연산자
단순 대입
int x = 42;
x = 100;
복합 대입
복합 대입 연산자는 산술 또는 비트 연산과 대입을 결합합니다. a op= b는 a = a op b와 동등합니다.
| 연산자 | 설명 | 동등한 표현 |
|---|---|---|
+= |
더하고 대입 | a = a + b |
-= |
빼고 대입 | a = a - b |
*= |
곱하고 대입 | a = a * b |
/= |
나누고 대입 | a = a / b |
%= |
나머지를 구하고 대입 | a = a % b |
&= |
비트 AND 후 대입 | a = a & b |
|= |
비트 OR 후 대입 | a = a | b |
^= |
비트 XOR 후 대입 | a = a ^ b |
<<= |
왼쪽 시프트 후 대입 | a = a << b |
>>= |
오른쪽 시프트 후 대입 | a = a >> b |
int x = 10;
x += 5; // x는 15
x -= 3; // x는 12
x *= 2; // x는 24
x /= 4; // x는 6
x %= 5; // x는 1
uint32_t flags = 0xFF00;
flags &= 0x0F0F; // flags는 0x0F00
flags |= 0x00F0; // flags는 0x0FF0
flags ^= 0x0FF0; // flags는 0x0000
비교 연산자
| 연산자 | 설명 | 예시 |
|---|---|---|
== |
같음 | a == b |
!= |
다름 | a != b |
< |
미만 | a < b |
> |
초과 | a > b |
<= |
이하 | a <= b |
>= |
이상 | a >= b |
모든 비교 연산자는 bool을 반환합니다.
논리 연산자
| 연산자 | 설명 | 예시 |
|---|---|---|
&& |
논리 AND (단락 평가) | a && b |
|| |
논리 OR (단락 평가) | a || b |
! |
논리 NOT | !a |
단락 평가(short-circuit evaluation): &&는 왼쪽 피연산자가 false이면 오른쪽 피연산자를 평가하지 않고, ||는 왼쪽 피연산자가 true이면 오른쪽 피연산자를 평가하지 않습니다.
// 단락 평가는 *평가 순서*에 영향을 줄 뿐, unsafe 역참조 규칙에는
// 영향을 주지 않습니다: &&가 ptr이 non-null일 때만 실행되도록 보장하더라도
// *ptr에는 여전히 'unsafe'가 필요합니다 — 컴파일러는 흐름 민감한
// 좁히기(narrowing)를 수행하지 않습니다.
unsafe {
if (ptr != (int*)0 && *ptr > 0) {
// &&' 덕분에 *ptr은 ptr이 non-null일 때만 평가됩니다
}
}
비트 연산자
| 연산자 | 설명 | 예시 |
|---|---|---|
& |
비트 AND | a & b |
| |
비트 OR | a | b |
^ |
비트 XOR | a ^ b |
~ |
비트 NOT (보수) | ~a |
<< |
왼쪽 시프트 | a << n |
>> |
오른쪽 시프트 | a >> n |
uint32_t flags = 0b1100; // 이진 리터럴 (리터럴과 한정자 참고); = 0xC
uint32_t mask = 0b1010; // = 0xA
uint32_t and_result = flags & mask; // 0x8 (0b1000)
uint32_t or_result = flags | mask; // 0xE (0b1110)
uint32_t xor_result = flags ^ mask; // 0x6 (0b0110)
uint32_t not_result = ~flags; // 모든 비트가 반전됨
uint32_t shifted = 1 << 4; // 16 (비트 4가 설정됨)
비트 조작 내장 함수
컴파일러는 GCC/Clang의 비트 내장 함수도 알고 있으며, 대부분의 타깃에서 각각 명령어 하나입니다.
| 내장 함수 | 결과 |
|---|---|
__builtin_popcount(x) / __builtin_popcountll(x) |
설정된 비트 수(int) |
__builtin_clz(x) / __builtin_clzll(x) |
앞쪽 0 비트 수(int; 0에 대해서는 정의되지 않음) |
__builtin_ctz(x) / __builtin_ctzll(x) |
뒤쪽 0 비트 수(int; 0에 대해서는 정의되지 않음) |
__builtin_bswap32(x) / __builtin_bswap64(x) |
바이트 순서를 뒤집은 x(x와 같은 타입) |
std/bit.h는 이들을 0에 대해서도 정의된 C23 <stdbit.h> 스타일 함수로 감쌉니다.
멤버 접근 연산자
| 연산자 | 설명 | 예시 |
|---|---|---|
. |
값 멤버 접근 | point.x |
-> |
포인터 멤버 접근 | ptr->x |
:: |
스코프 결정 / 메서드 정의 | Point::length() |
. 연산자는 값과 참조의 필드에 접근합니다. -> 연산자는 한 번에 포인터를 역참조하면서 필드에 접근합니다.
struct Point { double x; double y; };
struct Point p = {3.0, 4.0};
double x1 = p.x; // 값 접근
// raw pointer를 얻고 이를 ->로 역참조하는 것 모두 'unsafe'가 필요합니다 —
// '&stack Point'에서 'Point*'로의 암묵적 변환은 없습니다.
struct Point *ptr;
unsafe { ptr = (struct Point*)&p; }
double x2;
unsafe { x2 = ptr->x; } // 포인터 역참조 + 접근
// (*ptr).x와 동등함
:: 연산자는 메서드 정의와 정적 스코프 결정에 사용됩니다.
double Point::length() const {
return sqrt_d(self.x * self.x + self.y * self.y);
}
첨자 연산자
배열과 슬라이스의 인덱싱은 []를 사용합니다. 안전한 컨텍스트에서는 접근이 경계 검사됩니다.
int arr[5] = {10, 20, 30, 40, 50};
int x = arr[2]; // 30 (경계 검사됨)
[]int s = arr[1..4];
int y = s[0]; // 20 (슬라이스 길이를 기준으로 경계 검사됨)
삼항 연산자
조건(삼항) 연산자는 조건에 따라 두 표현식 중 하나를 평가합니다.
condition ? then_expr : else_expr
int x = 10;
int y = 20;
int max = (x > y) ? x : y; // max is 20
const &char label = (count == 1) ? "item" : "items";
삼항 연산자는 우선순위가 매우 낮습니다 — 복잡한 조건에는 명확성을 위해 괄호를 사용하십시오.
콤마 연산자
C와 동일하게 동작합니다: (e1, e2, ..., eN)은 모든 피연산자를 왼쪽에서
오른쪽으로 순서대로 평가하여 부수 효과를 발생시키며, 전체 표현식의
타입과 값은 마지막 피연산자의 것입니다 — 앞선 피연산자들의 값은
버려집니다.
int a; int b;
int x = (a = 1, b = 2, a + b); // x는 3
int i = 0;
int j = 10;
for (; i < j; i++, j--) {
// i는 증가, j는 감소
}
C 스타일의 콤마로 구분된 다중 변수 선언 역시 동작하며, for 루프의 초기화절로도 사용할 수 있습니다.
for (int i = 0, j = 10; i < j; i++, j--) {
// i는 증가, j는 감소
}
int a, b = 5, c; // a와 c는 초기화되지 않음, b는 5
주소 연산자와 역참조
| 연산자 | 설명 | 예시 |
|---|---|---|
& |
주소 연산자 (리전 한정 참조를 생성) | &x |
* |
역참조 (포인터/참조를 통해 값에 접근) | *ptr |
int x = 42;
&stack int ref = &x; // 리전 한정 참조
int y = *ref; // 역참조: y는 42
리전 한정 참조에 대한 자세한 내용은 메모리와 리전을 참고하십시오.
타입 관련 연산자
| 연산자 | 설명 | 예시 |
|---|---|---|
(T)expr |
명시적 캐스트 | (double)x |
sizeof(T) |
타입의 바이트 크기 | sizeof(int) |
sizeof(expr) |
표현식 타입의 바이트 크기 | sizeof(x) |
alignof(T) |
타입의 정렬 요구사항 | alignof(double) |
int x = 42;
double d = (double)x; // 명시적 캐스트
long s1 = sizeof(int); // 4
long s2 = sizeof(x); // 4
long a = alignof(double); // 8 (플랫폼에 따라 다름)
typeof, fieldcount, sizeof...는 컴파일 타임 인트로스펙션을 참고하십시오.
연산자 우선순위
높은 순위부터 낮은 순위까지:
| 우선순위 | 연산자 | 결합 방향 |
|---|---|---|
| 1 (최고) | () [] . -> x++ x-- |
왼쪽에서 오른쪽 |
| 2 | ++x --x +x -x ! ~ * & (T) sizeof alignof |
오른쪽에서 왼쪽 |
| 3 | * / % *| *% |
왼쪽에서 오른쪽 |
| 4 | + - +| -| +% -% |
왼쪽에서 오른쪽 |
| 5 | << >> |
왼쪽에서 오른쪽 |
| 6 | < <= > >= |
왼쪽에서 오른쪽 |
| 7 | == != |
왼쪽에서 오른쪽 |
| 8 | & |
왼쪽에서 오른쪽 |
| 9 | ^ |
왼쪽에서 오른쪽 |
| 10 | | |
왼쪽에서 오른쪽 |
| 11 | && |
왼쪽에서 오른쪽 |
| 12 | || |
왼쪽에서 오른쪽 |
| 13 | ? : |
오른쪽에서 왼쪽 |
| 14 | = += -= *= /= %= &= ^= |= <<= >>= |
오른쪽에서 왼쪽 |
| 15 (최저) | , |
왼쪽에서 오른쪽 |
연산자 오버로딩
구조체 타입은 operator+, operator- 등의 이름을 가진 메서드를 정의하여 이항 연산자를 오버로드할 수 있습니다. 자세한 내용은 함수를 참고하십시오.
오버로드 가능한 연산자: +, -, *, /, %, ==, !=, <, >, <=, >=.