[CS] L-Value vs R-Value

Dorae·2024년 10월 31일
0

CS

목록 보기
1/3

문제 인식

"Expression must be a modifiable lvalue"

위와 같은 오류를 다들 한 번씩은 마주친다.
VS 환경에서 마주치면 도대체 저게 어디서 나는 에러인지 파악하기조차 힘들다.
필자의 경우 lvalue의 l이 소문자 L인지, 대문자 I인지조차도 구분 못하고 있었다.

식 값 범주

  • C++17 표준은 다음과 같이 식 값 범주를 정의합니다.

  • glvalue는 평가에서 개체, 비트 필드 또는 함수의 ID를 결정하는 식입니다.

  • prvalue는 계산에서 개체 또는 비트 필드를 초기화하거나 표시되는 컨텍스트에 지정된 대로 연산자의 피연산자 값을 계산하는 식입니다.

  • xvalue는 리소스를 재사용할 수 있는 개체 또는 비트 필드를 나타내는 glvalue입니다(일반적으로 수명이 거의 다 되었으므로). 예: rvalue 참조(8.3.2)와 관련된 특정 종류의 식은 반환 형식이 rvalue 참조이거나 rvalue 참조 형식에 대한 캐스트인 함수에 대한 호출과 같이 xvalue를 생성합니다.

  • lvalue는 xvalue가 아닌 glvalue입니다.

  • rvalue는 prvalue 또는 xvalue입니다.

  • 이 부분에 대한 더 자세한 설명은 Values 문서에서 따로 진행하는 것으로 한다.

L-Value와 R-Value

간단한 설명

실제 에러메시지 등에서는 소문자를 사용한 표현 (lvalue, rvalue)로 나옵니다.

  • 첫 접근은 쉽게 하는 게 좋으니, 직관적으로 접근해보자.

    • L-Value의 경우 Left-Value, 즉 왼쪽 값이라는 의미가 된다.
    • R-Value의 경우 Right-Value, 즉 오른쪽 값이라는 의미가 된다.
  • 하지만 우리는 면접을 준비하고 있는 개발자 지망생이므로 조금 더 파고들어 보기로 한다.

탐구

  • C++에서 객체를 생성해 보자.
  int i;
  const int j;
  • 우리는 일종의 저장 공간을 선언하게 되었다.
    (int와 const int의 차이는, 이후에 값을 변경 가능한지의 여부에서 갈립니다.)

  • 이번에는 선언하면서 초기화를 진행해보자.
int i = 5 * 2;
  • 이때부터 Lvalue와 Rvalue의 구분이 생긴다.

  • 이 코드에서의 왼쪽 부분, 즉 int i로 선언되며 생기는 i는 Lvalue가 된다.
  • 이 코드에서의 오른쪽 부분, 즉 5 * 2 부분이 Rvalue가 된다.

결론

  • LValue는 객체를 참조하는 표현식. 즉 특정 메모리 위치를 가리킨다고 볼 수 있다.
  • RValue는 LValue를 제외한 모든 값이다.
    • C++ Standard에서는 RValue를 예외로 정의한다. 따라서, LValue가 아니라면 RValue가 되는 것이다.
    • 조금만 간단히 설명하자면, 메모리 위치를 기억할 필요가 없는 모든 값이 RValue이다.

Exception

int i = 1;
1 = i; // Error!
  • 위 코드를 보면 당연히 2번째 줄에서 에러가 난다고 판단할 것이다. 그런데 왜 에러가 나는지 명확하게 말하기가 까다롭다.

  • 정답을 말하자면, 등식의 왼쪽에 오는 값은 무조건 LValue여야 하기 때문이다.
    Incorrect usage: The left operand must be an lvalue (C2106).

  • 그럼 오른쪽에 LValue가 오는 것은 괜찮은가?

int i = 1;
int j = 1;
int i = j; // ?
  • 위 코드를 IDE에서 실행시켰을 때 오류가 나지 않을 것이다.
  • 식의 오른쪽에 LValue가 오는 것은 괜찮다.

LValue와 RValue 포인트

LValue 및 RValue에는 어떤 값들이 있는지 알아보자.

1. Numbers & Numeric Literals

int i = 1; // Numeric Literals
double j = 3.141592;
char k = 'a';
  • 1, 3.141592, 'a' 등은 전부 RValue이다.

2. Enum

enum Numbers { Zero, One, Two, Three };
One = Two; // Incorrect usage: The left operand must be an lvalue (C2106)
  • Enum 안에 있는 열거형 값들은 전부 RValue이다.

3. Binary Operator

int i = 1;
int j;
i + 1 = j; // Incorrect usage: The left operand must be an lvalue (C2106)
  • 이항 연산자(Binary Operator)의 결과물은 RValue이다.

4. Unary Operator : &

int i, *j;
j = &i; // Right Usage
i = &j; // Incorrect usage: the dereferenced pointer must be an lvalue
  • 단항 연산자 &의 피연산자는 LValue여야 한다.
  • 단항 연산자가 RValue이면 안 되나요?
    • 아래 예시를 한 번 생각해 보도록 하자.
&3 // ??????
  • 단항 연산자 &n의 연산 결과는 RValue이다.

5. Unary Operator : *

int a[3];
int *b = a;
int *b = 5; // Right usage
*(b + 1) = 7; // Right usage
  • &와는 다르게, *n의 연산 결과는 LValue이다.

6. Pre-increment/decrement operator

int Count = 0;
++nCount = 5; // Right usage
  • 놀랍게도 2번째 줄을 작성하고 실행시키면 맞는 표현임을 알 수 있다.
  • 따라서 전위 연산자의 연산 결과는 LValue이다.

7. Post-increment/decrement operator

int Count = 0;
nCount++ = 5; // Incorrect usage: The left operand must be an lvalue (C2106)
  • 후위 연산자로 같은 코드를 쓰면 에러가 난다.
  • 따라서 후위 연산자의 연산 결과는 RValue이다.

8. Returning references : 1

int& GetBig(int& a, int& b)    // returning reference to make the function call an Lvalue
{
    return ( a > b ? a : b );
}

void main()
{
    int i = 10, j = 50;
    GetBig( i, j ) *= 5;    
    // Here, j = 250. GetBig() returns the ref of j and it gets multiplied by 5 times.
} 
  • 함수의 반환형이 참조형(레퍼런스)일 때, 반환 결과값은 LValue이다.

9. Returning references : 2

int GetBig(int& a, int& b)    // returning an int to make the function call an Rvalue
{
    return ( a > b ? a : b );
}

void main()
{
    int i = 10, j = 50;
    const int& big = GetBig( i, j );
    // Here, I am binding 'big' an Lvalue to the return value from GetBig(), an Rvalue.

    int& big2 = GetBig(i, j); // Error. big2 is not binding to the return value as big2 is not const
} 
  • const의 사용에 따라 Error가 나는 부분이다.
  • 상단 코드의 경우 const에 의해, 참조만 하고 값 변경이 이루어지지 않는다.
  • 하단 코드의 경우 값이 변경되는데, 이 경우에 Error가 나게 된다.
  • 더 자세한 부분은 const 포스팅때 추가로 올리는 것으로 진행하려 한다.

LValue와 RValue 간의 변환

  • 대체 오른쪽에 LValue가 올 수 있는 이유가 무엇인가?
  • RValue가 필요한 맥락에 LValue가 들어오면, 컴파일러는 자동으로 LValue를 RValue로 바꿔준다.
  • LValue가 필요한 맥락에 RValue가 들어오는 것은 바꿔주지 않는다 (Error)
int i, j;
i = 3;
j = 6;
i = j; // Right usage

7 = a; // Incorrect usage: The left operand must be an lvalue (C2106)

<참고>

profile
게임 개발자 지망생

0개의 댓글