연산자
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 |
// 이진 리터럴 문법은 없습니다 (리터럴과 한정자 참고) — 대신 16진수
// 등가값을 쓰십시오. flags = 0xC = 0b1100, mask = 0xA = 0b1010.
uint32_t flags = 0xC;
uint32_t mask = 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가 설정됨)멤버 접근 연산자
| 연산자 | 설명 | 예시 |
|---|---|---|
. | 값 멤버 접근 | 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_exprint x = 10;
int y = 20;
int max = (x > y) ? x : y; // max는 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- 등의 이름을 가진 메서드를 정의하여 이항 연산자를 오버로드할 수 있습니다. 자세한 내용은 함수를 참고하십시오.
오버로드 가능한 연산자: +, -, *, /, %, ==, !=, <, >, <=, >=.