대입 연산자

대입 연산자 (Assignment operators)

대입 연산자는 왼쪽 변수의 값을 오른쪽 값으로 바꾸는 이항 연산자예요. = 하나만 떠올리기 쉽지만, C에는 += 같은 복합 대입 연산자도 있어요. 또 "배열은 대입할 수 없다"는 규칙도 이 연산자에서 나와요. 어떤 것이 대입 가능하고 어떤 것이 안 되는지, 반환 값은 무엇인지 정리해 볼게요.

출처: cppreference Assignment operators

본문

대입 연산자와 복합 대입 연산자는, 왼쪽에 있는 변수를 오른쪽에 있는 값을 사용해 수정하는 이항 연산자예요.

연산자 연산자 이름 설명 동등 표현
= 기본 대입 a=b ab와 같아짐 해당 없음
+= 덧셈 대입 a+=b aab의 합과 같아짐 a=a+b
-= 뺄셈 대입 a-=b aa에서 b를 뺀 값과 같아짐 a=a-b
*= 곱셈 대입 a*=b aab의 곱과 같아짐 a=a*b
/= 나눗셈 대입 a/=b aab로 나눈 값과 같아짐 a=a/b
%= 나머지 대입 a%=b aab로 나눈 나머지와 같아짐 a=a%b
&= 비트 AND 대입 a&=b aab의 비트 AND와 같아짐 a=a&b
|= 비트 OR 대입 a|=b aab의 비트 OR과 같아짐 a=a|b
^= 비트 XOR 대입 a^=b aab의 비트 XOR과 같아짐 a=a^b
<<= 비트 왼쪽 시프트 대입 a<<=b aab만큼 왼쪽 시프트한 값과 같아짐 a=a<<b
>>= 비트 오른쪽 시프트 대입 a>>=b aab만큼 오른쪽 시프트한 값과 같아짐 a=a>>b

단순 대입 (Simple assignment)

단순 대입 연산자 표현식의 형태는 다음과 같아요.

형태
lhs = rhs

대입은 rhs의 값에서 lhs의 타입으로 암시적 변환을 수행하고, 그다음 lhs가 지정하는 객체의 값을 변환된 rhs 값으로 바꿔치기해요.

대입은 또한 lhs에 저장된 것과 같은 값을 반환해요 (그래서 a=b=c 같은 표현식이 가능해요). 대입 연산자의 값 카테고리는 비-lvalue예요 (그래서 (a=b)=c 같은 표현식은 무효예요).

rhs와 lhs는 다음 중 하나를 만족해야 해요.

  • lhs와 rhs가 모두 호환되는 struct 또는 union 타입이거나...

  • rhs가 lhs로 암시적으로 변환 가능해야 해요. 이는 다음을 함의해요.

    • lhs와 rhs가 모두 산술 타입인 경우. 이때 lhs는 volatile 한정 또는 atomic(C11부터)일 수 있어요.
    • lhs와 rhs가 모두 (한정자를 무시하고) 호환되는 타입에 대한 포인터이거나, 둘 중 하나가 void에 대한 포인터인 경우. 그리고 변환이 가리켜지는 타입에 한정자를 추가하지 않는 경우. lhs는 volatile 또는 restrict(C99부터) 한정 또는 atomic(C11부터)일 수 있어요.
    • lhs가 (한정 또는 atomic(C11부터)일 수 있는) 포인터이고 rhs가 NULL 같은 널 포인터 상수 또는 nullptr_t 값(C23부터)인 경우
    • lhs가 (한정 또는 atomic일 수 있는) _Bool 타입이고 rhs가 포인터 또는 nullptr_t 값(C23부터)인 경우 (C99부터)
    • lhs가 (한정 또는 atomic일 수 있는) nullptr_t 타입이고 rhs가 nullptr_t 타입인 경우 (C23부터)

참고 (Notes)

rhs와 lhs가 메모리에서 겹치면(예: 같은 union의 멤버들), 겹침이 정확하고 타입이 호환되는 경우가 아니면 동작은 정의되지 않아요.

배열은 대입할 수 없지만, 구조체로 감싼 배열은 같은(또는 호환되는) struct 타입의 다른 객체에 대입할 수 있어요.

lhs를 갱신하는 부작용은 값 계산들 **다음에 시퀀싱(sequenced after)**되지만, lhs와 rhs 자체의 부작용은 아니에요. 그리고 피연산자의 평가는 평소처럼 서로에 대해 비시퀀싱(unsequenced)돼요 (그래서 i=++i; 같은 표현식은 정의되지 않아요).

대입은 부동소수점 표현식에서 여분의 범위와 정밀도를 제거해요 (FLT_EVAL_METHOD 참고).

C++에서는 대입 연산자가 lvalue 표현식이지만, C에서는 그렇지 않아요.

#include <stdio.h>

int main(void)
{
    // integers
    int i = 1, j = 2, k = 3; // initialization, not assignment
    i = j = k; // values of i and j are now 3
    // (i = j) = k; // Error: lvalue required
    printf("%d %d %d\n", i, j, k);

    // pointers
    const char c = 'A'; // initialization; not assignment
    const char *p = &c; // initialization; not assignment
    const char **cpp = &p; // initialization; not assignment
    // cpp = &p; // Error: char** is not convertible to const char**
    *cpp = &c; // OK, char* is convertible to const char*
    printf("%c\n", **cpp);
    cpp = 0; // OK, null pointer constant is convertible to any pointer

    // arrays
    int arr1[2] = {1, 2}, arr2[2] = {3, 4};
    // arr1 = arr2; // Error: cannot assign to an array
    printf("arr1[0]=%d arr1[1]=%d arr2[0]=%d arr2[1]=%d\n",
            arr1[0], arr1[1], arr2[0], arr2[1]);

    struct { int arr[2]; } sam1 = {{5, 6}}, sam2 = {{7, 8}};
    sam1 = sam2; // OK: can assign arrays wrapped in structs
    printf("%d %d\n", sam1.arr[0], sam1.arr[1]);
}

출력:

3 3 3
A
arr1[0]=1 arr1[1]=2 arr2[0]=3 arr2[1]=4
7 8

복합 대입 (Compound assignment)

복합 대입 연산자 표현식의 형태는 다음과 같아요.

형태
lhs op rhs

여기서

op - *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= 중 하나
lhs, rhs - 산술 타입의 표현식 (lhs는 한정 또는 atomic일 수 있음). 단 +=-=가 연산자일 때는 +-와 같은 제약으로 포인터 타입도 받아들여요

표현식 lhs@=rhslhs = lhs@(rhs)와 정확히 같아요. 단 lhs가 한 번만 평가된다는 점이 달라요.

lhs가 atomic 타입이면, 그 연산은 메모리 순서 memory_order_seq_cst를 가진 단일 원자 읽기-수정-쓰기(read-modify-write) 연산처럼 동작해요. 정수 원자 타입의 경우 복합 대입 @=는 다음과 동등해요:

T1 *addr = &lhs;
T2 val = rhs;
T1 old = *addr;
T1 new_;
do { new_ = old @ val; } while (!atomic_compare_exchange_strong(addr, &old, new_));

(C11부터)

#include <stdio.h>

int main(void)
{
    int x = 10;
    int hundred = 100;
    int ten = 10;
    int fifty = 50;

    printf("%d %d %d %d\n", x, hundred, ten, fifty);

    hundred *= x;
    ten /= x;
    fifty %= x;

    printf("%d %d %d %d\n", x, hundred, ten, fifty);

    return 0;
}

출력:

10 100 10 50
10 1000 1 0

더 알아보기

  • 대입 연산자는 할당 시 부작용·시퀀싱 규칙을 다루는 평가 순서 문서와 이어져요.
  • 구조체 대입과 원자 대입은 구조체·원자 연산 문서에서 더 깊게 볼 수 있어요.
  • cppreference의 Assignment operators 원문에서 표준 절 번호를 더 확인할 수 있어요.