ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • L-value와 R-value
    Programming🧑‍💻/Cpp 2025. 2. 6. 21:43

    표현식 ( Expression )

    표현식이란, 하나의 값으로 평가될 수 있는 코드의 단위입니다. 

    아래 코드에서 표현식은 계산되어 하나의 값이됩니다. 

    int a = 5;          // 5는 표현식
    int b = a + 3;      // a + 3은 표현식
    int c = b * (a-1);  // b * (a-1)은 표현식, (a-1)도 표현식

     

    L-Value와 R-Value 개념

    L-value와 R-value는 표현식(expression)의 값 범주를 구분하는 방법입니다. 

     

    L-Value의 'L'은 'Left'에서 왔습니다. 이는 할당 연산자(=)의 왼쪽에 올 수 있는 표현식을 의미합니다. 

    L-Value의 핵심적인 특징은 메모리상에서 이름과 주소를 가지고 있어 여러번 참조 할 수 있다는 점입니다.

    int x = 10;  // x는 L-value

     

    위의 코드에서 x는 L-value입니다. x라는 변수는 메모리상에 특정 위치를 가리키는 이름이 있고, 프로그램이 실행되는 동안 그 위치에 계속 존재하기 때문에 같은 범위내에서 활용이 가능합니다. 

     

    R-Value의 'R'은 'Right'에서 왔습니다. 이는 할당 연산자(=) 오른쪽에 올 수 있는 표현식을 의미합니다. 

    일반적으로 임시적인 값을 의미하며 표현식이 예산된 후 곧바로 사라지는 임시값이기 때문에 다음에 사용이 불가능합니다. 

    int x = 10;  // 10은 R-value
    int y = x + 5;  // x + 5는 R-value

     

     

    세 가지 매개변수 전달 방식

    값에 의한 전달 ( Pass by Value )

    • 함수 호출 시 매개변수의 완전한 복사본이 생성됩니다
    • 함수 내부에서 다시 한 번 복사가 일어나므로, 총 두 번의 복사가 발생합니다
    • 원본 데이터는 안전하게 보호되지만, 메모리와 성능 측면에서는 비효율적입니다

     

    int main() {
        // 1단계: normalStr 생성
        std::string normalStr = "Hello World";
        /* 이때 일어나는 일:
           - "Hello World" 문자열 리터럴은 실제로 프로그램의 상수 영역에 저장됩니다
           - std::string 객체는 스택에 생성됩니다 (normalStr)
           - 실제 문자열 데이터의 복사본이 힙에 할당됩니다
           - normalStr은 이 힙 메모리를 가리키게 됩니다 */
    
        storeByValue(normalStr);
        // 함수 호출이 끝나면 s와 b는 소멸되며, 그들이 가리키던 힙 메모리도 해제됩니다
    }
    
    void storeByValue(std::string s) {
        /* 2단계: 매개변수 s 생성
           - s는 새로운 std::string 객체로 스택에 생성됩니다
           - normalStr이 가리키는 문자열의 새로운 복사본이 힙에 생성됩니다
           - s는 이 새로운 힙 메모리를 가리킵니다 */
    
        std::string b = s;
        /* 3단계: 지역변수 b 생성
           - b는 새로운 std::string 객체로 스택에 생성됩니다
           - s가 가리키는 문자열의 또 다른 복사본이 힙에 생성됩니다
           - b는 이 새로운 힙 메모리를 가리킵니다 */
    } // 함수가 끝나면 b와 s가 소멸되며, 각각이 가리키던 힙 메모리도 해제됩니다
    [프로그램 시작 시점]
    상수 영역: "Hello World" (문자열 리터럴)
    
    스택                          힙
                                 
    main() {
      normalStr --------------> ["Hello World"] (첫 번째 복사)
    }
    
    [storeByValue 함수 호출 시점]
    상수 영역: "Hello World"
    
    스택                          힙
    main() {
      normalStr --------------> ["Hello World"] (그대로 유지)
    }
    storeByValue() {
      s ----------------------> ["Hello World"] (두 번째 복사)
    }
    
    [함수 내 b 생성 시점]
    상수 영역: "Hello World"
    
    스택                          힙
    main() {
      normalStr --------------> ["Hello World"] (그대로 유지)
    }
    storeByValue() {
      s ----------------------> ["Hello World"] (두 번째 복사)
      b ----------------------> ["Hello World"] (세 번째 복사)
    }

     

    L-Value Reference 

     

    • 함수 호출 시 복사가 발생하지 않고 원본의 메모리 주소만 전달됩니다
    • 함수 내부에서 한 번의 복사만 발생합니다
    • 원본 데이터를 수정할 수 있으므로 주의가 필요합니다

     

    void storeByLRef(std::string &s) {
        std::string b = s;  // 여기서만 새로운 복사가 일어납니다
    }
    
    int main() {
        std::string normalStr = "Hello World";
        storeByLRef(normalStr);
    }
    
    // 1단계: main 함수에서 normalStr 생성
    std::string normalStr = "Hello World";
    /* 이때 일어나는 일:
       - "Hello World" 문자열 리터럴은 프로그램의 상수 영역에 저장
       - normalStr 객체가 스택에 생성됨
       - 문자열 데이터가 힙에 복사됨
       - normalStr이 이 힙 메모리를 가리킴 */
    
    // 2단계: std::move(normalStr) 실행
    storeByRRef(std::move(normalStr));
    /* 이때 일어나는 일:
       - normalStr의 리소스 소유권이 s로 이동됨
       - normalStr은 유효하지만 비어있는 상태가 됨
       - 힙 메모리의 실제 복사는 일어나지 않음 */
    
    void storeByRRef(std::string &&s) {
        std::string b = std::move(s);
        /* 이때 일어나는 일:
           - b는 새로운 std::string 객체로 스택에 생성됨
           - s가 가리키던 힙 메모리의 소유권이 b로 이동됨
           - s는 비어있는 상태가 됨
           - 힙 메모리의 실제 복사는 일어나지 않음 */
    }
    [프로그램 시작 시점]
    상수 영역: "Hello World"
    
    스택                          힙
    main() {
      normalStr --------------> ["Hello World"] (첫 번째 복사)
    }
    
    [storeByLRef 함수 호출 시점]
    상수 영역: "Hello World"
    
    스택                          힙
    main() {
      normalStr ----------┐---> ["Hello World"] (복사 없음)
    }                     │
    storeByLRef() {      │
      s (참조) ----------┘
    }
    
    [함수 내 b 생성 시점]
    상수 영역: "Hello World"
    
    스택                          힙
    main() {
      normalStr ----------┐---> ["Hello World"] (원본 유지)
    }                     │
    storeByLRef() {      │
      s (참조) ----------┘
      b -----------------------> ["Hello World"] (한 번의 복사)
    }

     

    R-Value Reference 

     

    • 임시 객체나 std::move된 객체를 받을 수 있습니다
    • 메모리 복사 없이 리소스 소유권만 이전됩니다
    • 가장 효율적이지만, 원본 데이터는 이동 후 유효하지 않게 됩니다

     

    void storeByRRef(std::string &&s) {
        std::string b = std::move(s);  // 이동 연산: 힙 메모리의 소유권만 이전됨
    }
    
    int main() {
        std::string normalStr = "Hello World";
        storeByRRef(std::move(normalStr));
    }
    
    // 1단계: main 함수에서 normalStr 생성
    std::string normalStr = "Hello World";
    /* 이때 일어나는 일:
       - "Hello World" 문자열 리터럴은 프로그램의 상수 영역에 저장
       - normalStr 객체가 스택에 생성됨
       - 문자열 데이터가 힙에 복사됨
       - normalStr이 이 힙 메모리를 가리킴 */
    
    // 2단계: std::move(normalStr) 실행
    storeByRRef(std::move(normalStr));
    /* 이때 일어나는 일:
       - normalStr의 리소스 소유권이 s로 이동됨
       - normalStr은 유효하지만 비어있는 상태가 됨
       - 힙 메모리의 실제 복사는 일어나지 않음 */
    
    void storeByRRef(std::string &&s) {
        std::string b = std::move(s);
        /* 이때 일어나는 일:
           - b는 새로운 std::string 객체로 스택에 생성됨
           - s가 가리키던 힙 메모리의 소유권이 b로 이동됨
           - s는 비어있는 상태가 됨
           - 힙 메모리의 실제 복사는 일어나지 않음 */
    }
    [프로그램 시작 시점]
    상수 영역: "Hello World"
    
    스택                          힙
    main() {
      normalStr --------------> ["Hello World"] (첫 번째 복사)
    }
    
    [std::move 호출 후]
    상수 영역: "Hello World"
    
    스택                          힙
    main() {
      normalStr (비어있음)        ["Hello World"]
    }                            ↑
    storeByRRef() {             │
      s ------------------------|  (소유권 이전, 복사 없음)
    }
    
    [함수 내 b = std::move(s) 실행 시]
    상수 영역: "Hello World"
    
    스택                          힙
    main() {
      normalStr (비어있음)        ["Hello World"]
    }                            ↑
    storeByRRef() {             │
      s (비어있음)               │
      b ------------------------|  (소유권 이전, 복사 없음)
    }

    댓글

Designed by Tistory.