-
L-value와 R-valueProgramming🧑💻/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 ------------------------| (소유권 이전, 복사 없음) }
'Programming🧑💻 > Cpp' 카테고리의 다른 글
빌드 과정과 #include의 역할 이해하기 (0) 2025.02.12 헤더파일은 뭐고 왜 필요한가? (0) 2025.02.12 함수 호출시 매개변수 전달 방법 3가지 (0) 2025.02.05 콜 스택 동작을 통해 알아보는 함수 호출 (0) 2025.02.04 포인터의 크기는 어떻게 결정될까? (0) 2025.02.03