[C++] Lvalue와 Rvalue란?


C++ 문법을 보다보니 lvalue와 rvalue라는 용어를 발견했습니다.

아마 컴파일을 하다보면 rvalue와 lvalue에 관한 오류가 한번쯤 발생한걸 보신분들도 있을겁니다.

어떤 의미일까? 하여 관련 내용을 찾아보고 학습한 내용을 기록합니다.

 

일반적으로 표준 C언어의 관점에서는 Lvalue는 Left-value, Rvalue는 Right-value로 왼쪽에 있는 값, 오른쪽에 있는 값으로 이해할 수 있는데 대입연산자 (=) 를 기준으로 왼쪽에 있으면 left value, 오른쪽에 있으면 right value 라는 직관적이고 심플한 의미를 가지고 있습니다.

 

그러나 C++ 표준에서는 더이상 L과 R이 Left와 Right 의 의미를 가지고 있지 않습니다. 그러므로 앞에서 언급한 Left, Right의 개념은 모두 잊고 Lvalue와 Rvalue는 고유명사로써, 즉 단어 그대로 특별한 의미를 가지고 있다고 생각하셔야 합니다.

 

Lvalue와 Rvalue 구분하기

 

Lvalue

표현식 이후에도 사라지지 않는 값, 이름을 지니는 변수.

*const 타입을 포함한 모든 변수는 Lvalue이다!

 

Rvalue

표현식 이후에는 사라지는 임시적인 값, 임시 변수.

 

우선 Lvalue와 Rvalue의 정의입니다.

C++에서 모든 표현식은 Lvalue 또는 Rvalue라고 생각하시면 됩니다.

정의만으로는 햇갈리니 아래 예시를 통해 좀더 자세히 알아보겠습니다.

 

 

명시적으로 선언하여 '이름' 을 가지고 계속 접근할 수 있는 변수의 경우 Lvalue입니다.

그러므로 x, y, z, p 등의 이름을 가지는 변수는 모두 Lvalue 가 되겠네요.

 

위 사진에서 밑줄 쳐진 부분이 바로 Rvalue 입니다.

상수값 3, 임시 객체 string("one") 은 표현식이 종료되버리면 더이상 참조할 수 없는 값이 되버립니다. 

 

그리고 x+y 와 같은 산술 연산의 경우에도 x+y 따위는 실제 변수가 아니라 임시 변수를 생성하여 레지스터 or 메모리에 저장, 사용합니다. 이 값은 일반 변수와 마찬가지로 int 등의 타입을 가지고 있지만 계산이 끝나면 사라지게 됩니다. (Rvalue)

 

Lvalue에 약간의 연산을 가한다던가 &x와 같은 표현식도 따로 저장되어 있는 변수가 아니라 '연산된 결과' 로써 전부 Rvalue라고 볼 수 있겠습니다. (함수의 리턴값 또한 Rvalue 입니다.)

 

그런데 증감연산자에 있어서 전위냐, 후위냐에 따라 Lvalue와 Rvalue가 나뉘게 됩니다.

예를 들어서,

 

++x; //lvalue
x++; //rvalue

전위 증감연산자를 사용한 ++x는 Lvalue인데, 후위 증감연산자를 사용한 x++의 경우 Rvalue 라고 합니다 (??)

 

int a = b++;

다음코드를 한번 살펴봅시다.

일반적으로는 이 코드를 우선 a라는 변수에 b의 값을 저장하고 이후에 b를 1증가 시킨다 정도로 이해하시고 계시겠지만 보다 정확히는

 

b의 값을 복사해서 임시변수(rvalue, 임시값)에 저장하고, 원래 b의 값을 변경, 그리고 임시변수를 계산에 사용한다는 매커니즘으로 작동합니다.

 

그래서 x++와 같은 경우 rvalue이고, 반면에 ++x는 증가된 x 자기 자신을 리턴하기 때문에 Lvalue 입니다.

물론 이런 메커니즘을 웬만해선 알긴 어렵고 매번 따지기도 힘들죠.

 

그래서 Lvalue와 Rvalue 판정을 위해선 주소 연산자(&) 를 이용해보면 됩니다.

&연산자는 lvalue를 요구하기 때문에 표현식이 rvalue라면 컴파일 오류가 발생합니다.

 

#include <stdio.h>
int main()
{
	int x = 10;
	&(x++); //error!
	&(++x);
	return 0;
}

main.cpp 로 빈 cpp파일을 만들고 다음과 같이 소스코드를 작성해보세요.

 

(저렇게 그냥 연산자만 적어주고 변수에 저장을 안하는게 좀 어색해보이실 수 있는데,

저렇게 연산만 해주고 변수에 따로 저장안하면 계산만하고 따로 저장은 안하고 그냥 넘어간다고 보시면 됩니다.)

 

저 &x++; 라는 부분에서 아래와 같은 컴파일 오류가 발생하는걸 알 수 있습니다.

* 괄호 치는걸 잊지마세요!

[Error] lvalue required as unary '&' operand

단항 연산자(피연산자가 1개)인 &은 lvalue를 필요로 한답니다.

x++에서 & 연산을 걸었더니 오류가 난다는건 x++가 rvaule 란 뜻이겠죠.

 

Rvalue 참조자 &&

C언어에서는 어떤 값에 메모리 적으로 직접 접근하는 방법으로 '포인터' 라는 것을 제공했습니다.

C++에서는 이 포인터로 발생하는 여러 문제들이나 복잡성 (없는 메모리를 접근한다던지, 포인터 변수로 접근할때 * 연산자나 & 연산자를 항상 써줘야 한다든지 등)이 마음에 들지 않았는지 레퍼런스라는 새로운 개념을 통해

값을 참조할 수 있습니다.

 

레퍼런스에 관해 잘 모르신다면 아래 링크를 참고해주세요

https://pgh268400.tistory.com/414

 

[C++] 참조자 (레퍼런스, Reference)

기존 포인터 연산 #include using std::cout; using std::endl; int change_val(int * p){ *p = 3; //p주소를 찾아가 해당하는 값(val)에 3을 대입한다. } int main(){ int num = 5; cout << "num : " << num << e..

pgh268400.tistory.com

 

 

int& a = b;

위와 같이 b라는 변수에 a라는 별명을 붙이는 참조자(Reference) 는 Lvalue 참조자입니다.

 

int& a = (b++);

참조자를 쓸때 용도 자체가 뭐 당연한것이기도 하지만 위처럼 b++ 와 같은 rvalue 를 넣어버리면 얄짤없이 rvalue 가 들어갔다고 컴파일 오류가 뜹니다.

 

C++ 11 표준에서는 Lvalue 참조자 이외에도 Rvalue를 참조할 수 있는 Rvalue 참조자가 추가되었습니다.

 

이름에서 알 수 있듯 Lvalue 참조자는 Lvalue만 참조할 수 있고,

Rvalue 참조자는 Rvalue만 참조할 수 있습니다.

 

사실 표현식이 종료되면 더이상 존재하지 않는 임시적인 값을 참조해서 어따쓸까 의문이긴 합니다만.. (대표적으로 이동 생성자와 이동 대입 구현에 사용합니다.)

Rvalue 참조자에 관한 자세한 내용은 여기서 다 적긴 어려우므로 우선 생략하도록 하고

이 글은 Lvalue랑 Rvalue에 대해 이해하기 위한 글이니 우선 'Rvalue 참조자 같은것이 있다' 정도만 이해하고 넘어가도록 합시다.

 

우선 저는 dev c++을 쓰고 있는데 Rvalue 참조자가 c++ 11 표준에서 추가됬으므로 저렇게 컴파일러에게 c++11 표준으로 컴파일 하고 싶다고 알려줍시다.

 

int rvalue()
{
    return 10; //리턴값 == rvalue 
}

int main()
{
    int lvalue = 10;

    int& a = lvalue;
    //int& b = rvalue();      error : invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'

    //int&& c = lvalue;        error cannot bind 'int' lvalue to 'int&&'
    int&& d = rvalue();
}

오류가 나는 부분만을 주석처리 해놓고 에러 메세지를 표기해뒀습니다.

아까도 말했듯이 lvalue 참조자는 rvalue 참조가 불가능하고

rvalue 참조자의 경우 lvalue 참조가 불가능합니다.

 

 

참고

https://blog.naver.com/luku756/221808884092

https://effort4137.tistory.com/entry/Lvalue-Rvalue

http://hellocbc.blogspot.com/2021/01/c-lvalue-rvalue.html

 

파이썬 하고싶다

COMMENT WRITE