[C/C++] Value Category

할랑말랑·2026년 3월 10일

C/C++

목록 보기
14/45

Value Category

C++에서 모든 표현식(expression)은 타입과 값 범주(Value category) 두 가지 속성을 가진다.

1. glvalue (generalized lvalue)

"위치를 가진 값" - 메모리 주소를 가질 수 있는 표현식

특징

  • 주소를 가질 수 있음(& 연산자 사용 가능)
  • 식별자(identity)를 가짐
  • lvalue + xvalue
#include <iostream>
using namespace std;

int main()
{
    int x = 10;
    int* ptr = &x;           // x는 glvalue (주소를 가질 수 있음)

    int& ref = x;
    int* ptr2 = &ref;        // ref도 glvalue

    int arr[3] = { 1, 2, 3 };
    int* ptr3 = &arr[0];     // arr[0]도 glvalue

    return 0;
}

2. rvalue (right value)

"임시 값" - 곧 사라질 값

특징

  • 주소를 가질 수 없음
  • 대입 연산자의 오른쪽에만 올 수 있음
  • prvalue + xvalue
#include <iostream>
using namespace std;

int main()
{
    int x = 10;

    // int* ptr = &10;         // 에러: 10은 rvalue (주소 없음)
    // int* ptr2 = &(x + 5);   // 에러: x+5는 rvalue

    int y = x + 5;           // OK: rvalue를 대입 연산자 오른쪽에 사용

    return 0;
}

3. lvalue (left value)

"이름이 있고" 지속되는 객체

특징

  • 변수명을 가짐
  • 주소를 가질 수 있음
  • 표현식이 끝나도 살아있음
  • 대입 연산자의 왼쪽에 올 수 있음
#include <iostream>
using namespace std;

int globalVar = 100;

int& getRef()
{
    return globalVar;
}

int main() 
{

    int x = 10;           // 1. 변수
                          // x는 lvalue

    int arr[3] = { 1, 2, 3 };
    arr[0] = 5;           // 2. 배열 요소
                          // arr[0]는 lvalue

    const char* str = "Hello"; 
                          // 3. 문자열 리터럴
                          // "Hello"는 lvalue (특수 케이스)

    getRef() = 200;       // 4. 참조를 반환하는 함수
                          // getRef()는 lvalue

    int y = 5;
    ++y = 10;             // 5. 전위 증가 연산자
                          // ++y는 lvalue (y 자체를 반환)

    int* ptr = &x;
    *ptr = 20;            // 6. 역참조 연산자
                          // *ptr은 lvalue

    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    cout << "globalVar = " << globalVar << endl;

    return 0;
}

문자열 리터럴은 lvalue

#include <iostream>
using namespace std;

int main()
{
    // 문자열 리터럴은 lvalue!
    const char* ptr = "Hello";  // OK

    // 주소를 가질 수 있음!
    cout << "문자열 주소: " << (void*)"Hello" << endl;
    cout << "같은 문자열 주소: " << (void*)"Hello" << endl;  // 같은 주소!

    // 배열처럼 접근 가능
    cout << "첫 글자: " << "Hello"[0] << endl;  // 'H'

    // 포인터 연산 가능
    cout << "두 번째 글자: " << *("Hello" + 1) << endl;  // 'e'

    return 0;
}
#include <iostream>
using namespace std;

int main()
{
    // 문자열 리터럴은 lvalue!
    const char* ptr = "Hello";  // OK

    // 주소를 가질 수 있음!
    cout << "주소: " << (void*)"Hello" << endl;  // OK

    // 참조로 받을 수 있음!
    const char(&ref)[6] = "Hello";  // OK (배열 참조)

    cout << ref << endl;

    return 0;
}

정적 메모리 영역에 저장됨

4. prvalue (pure rvalue)

"순수한 임시 값" - 위치가 없는 값

특징

  • 주소가 없음
  • 임시로 생성되어 곧 사라짐
  • 객체를 초기화하거나 연산에 사용됨
#include <iostream>
using namespace std;

class Number
{
public:
    int value;

    Number(int v) : value(v)
    {
        cout << "생성자: " << value << " (this: " << this << ")" << endl;
    }

    ~Number()
    {
        cout << "소멸자: " << value << " (this: " << this << ")" << endl;
    }
};

Number createNumber()
{
    return Number(100);  // Number(100)는 prvalue
}

int getValue()
{
    return 42;  // 42는 prvalue
}

int main()
{
    // 1. 리터럴
    int x = 10;          // 10은 prvalue

    // 2. 산술 연산 결과
    int y = 5 + 3;       // 5+3은 prvalue

    // 3. 후위 증가 연산자
    int z = 7;
    int w = z++;         // z++는 prvalue (임시 값 반환)

    // 4. 값을 반환하는 함수
    int result = getValue();  // getValue()는 prvalue

    // 5. 임시 객체
    Number n = createNumber();  // createNumber()는 prvalue

    // 6. 임시적 생성자 호출
    Number temp = Number(50);   // Number(50)는 prvalue

    // 7. 캐스팅
    double d = static_cast<double>(10);  // static_cast<double>(10)는 prvalue

    // 8. this 포인터
    // this는 prvalue (포인터 값 자체)

    return 0;
}

5. xvalue (expiring value)

"만료되는 값" - 곧 사라질 객체지만 위치는 있음

특징

  • 주소를 가질 수 있음(glvalue)
  • 곧 사라질 예정(rvalue)
  • 이동 가능한 객체
  • std::move의 결과
#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <utility>

using namespace std;

class String
{
private:
    char* data;

public:
    String(const char* str)
    {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
        cout << "생성자: " << data << " (주소: " << (void*)data << ")" << endl;
    }

    ~String() 
    {
        cout << "소멸자: " << (data ? data : "nullptr") << endl;
        delete[] data;
    }

    String(String&& other) noexcept
    {
        cout << "이동 생성자 호출!" << endl;
        data = other.data;
        other.data = nullptr;
    }
};

String&& getRvalueRef()
{
    static String s("Static");
    return std::move(s);  // xvalue 반환
}

int main()
{
    // 1. std::move의 결과
    String s1("Hello");
    String s2 = std::move(s1);  // std::move(s1)는 xvalue

    // 2. rvalue 참조로 캐스팅
    String s3("World");
    String s4 = static_cast<String&&>(s3);  // xvalue

    // 3. rvalue 참조를 반환하는 함수
    String s5 = getRvalueRef();  // xvalue

    // 4. 배열의 이동
    String arr[2] = { String("A"), String("B") };
    String s6 = std::move(arr[0]);  // std::move(arr[0])는 xvalue

    return 0;
}

핵심정리

0개의 댓글