이게 c에는 없다는 call by reference가 맞나요?
글쓴이: awdxawdx101 / 작성시간: 토, 2020/04/04 - 5:36오후
c언어에는 없다는 call by reference를 사용해도, 결국 exe가 되는 과정에서 asm으로 바뀔 것입니다.
그리고 asm으로 바뀐건 c로도 구현가능할 것 같다고 생각했습니다.
그래서 call by reference 예제 코드를 작성하고 asm코드를 확인해보고 싶은데 아래의 코드가 call by reference의 예시가 될까요?
#include <iostream> using namespace std; void changeN(int& ref){ ref++; } int main(void){ int n = 5; cout << "before : "; cout << n << endl; changeN(n); cout << "after : "; cout << n << endl; return 0; }
Forums:
1. 실제로 해보면 십중팔구 changeN(n)
1. 실제로 해보면 십중팔구 changeN(n) 부분에 n++가 들어간 것처럼 컴파일 될 겁니다.
inline이 없어도 컴파일러가 알아서 inlining 해 버리는 거죠.
그런 최적화를 못 하게 옵션을 준 경우라면 아마 지역 변수 n의 포인터가 넘어가는 형태로 컴파일 될 거에요.
2. 해당 떡밥은 수 년 전 kldp 한 구석을 불태웠던 유구한 떡밥이지요.
제 생각에 그건 "C언어의 syntax/semantic에 의해 직접적으로 제공되는 기능"과 "C언어가 제공하는 기능을 이용해서 구현 가능한 기능" 사이의 구분에 더 가까운 문제인 것 같습니다.
Call by reference는 전자는 아니지만 후자에는 명백히 들어간다고 생각하는데요.
"C언어에 call by reference가 있다/없다"고 할 때, 전자와 후자 중 어떤 것으로 해석해야 하는지는 논란의 여지가 있겠죠.
근데 그런 논란에는 솔직히 뭐 별로 끼고 싶지 않습니다...
3. asm으로 가능한 건 c로 다 가능한가?
C언어는 확실히 강력한 언어이고, 그 구현 능력에 거의 한계가 없어 보이긴 합니다.
그래도 몇 가지 어려워 보이는 일이 있긴 한 것 같은데요.
대표적으로, 일부 명령어 확장 집합의 명령어들은 C언어로는 직접 기술할 방법이 없습니다.
다만,
(1) inline assembly를 쓰거나,
(2) 그 명령어를 wrapping한 라이브러리 함수를 호출하거나,
(3) 일부 컴파일러는 그런 명령어를 직접 사용할 수 있도록 Compiler Intrinsic Equivalent을 제공하기도 하죠.
(4) 한 가지 더 있네요. 기계어 코드로 때려박고 함수 포인터로 캐스팅해서 호출
하면 가능하긴 하니까, 이런 것도 "지원"의 범위에 들어간다고 할 수 있는 걸까요...
c언어로 주소를 넘겨준 후 컴파일 하고 asm을 봤을
c언어로 주소를 넘겨준 후 컴파일 하고 asm을 봤을 때 코드가 똑같아서 이해가 되질 않습니다.
똑같이 인자로 주소를 넘겨주고 함수쪽에서는 주소를 참조해 ++연산을 하더라구요.
일단 궁금한게 cpp을 배운지 얼마되지않아, 위 코드가 call by reference의 예시인지 모르겠습니다.
...
call by reference의 전형적인 예입니다. 그리고 위의 익명 사용자님이 충분히 대답을 잘 해주셨습니다.
asm으로 코드가 동일하게 생성된다고 해서 소스 코드가 같았다고 할 수는 없습니다. g++ 9.3에 -O2 버전으로는 아래 두 코드가 완전히 동일한 바이너리를 만듭니다.
실제로 어떻게 돌아가는지를 설명하려면 굉장히 이야기가 길고 어려워질 것 같습니다. 다른 분들도 대답을 잘 해주셨구요.
정확하지는 않지만 단순화 해서 이렇게 생각해 보셔도 될 것 같습니다.
reference가 정의되는 경우는 두 가지가 있겠죠. 함수의 parameter로 넘어오는 경우 (changeId)와 아래와 같이 어딘가에 정의되는 경우입니다,
후자의 경우, ref가 bar() 안에서만 사용되고 탈출하지 않는다면, 사실 ref 대신 x를 바꿔 써줘도 무방하죠. 그래서 컴파일러는 컴파일 전에 그렇게 합니다. (거듭 말씀드리지만, 실제로는 그렇지 않지만 이해를 돕기 위해 이렇게 단순화 합시다.)
본문처럼 parameter로 넘어올 경우는 얘기가 다릅니다. 이 경우에는 callee는 그 reference에 뭐가 들어 있는지 정확히 모르게 되죠. (앞선 예에선 컴파일러는 ref가 x임을 알고요)
그래서 컴파일러는 컴파일을 하기 전에 소스 코드 상에서 reference를 pointer로 바꿉니다. callee 쪽에서는 int&를 int*로 바꾸고, ref를 *ref로 바꾸는 식이고요. caller 쪽에서는 n이 넘어간다면, n 대신 &n을 넘기는 거죠.
그렇게 하면 의미가 동일한 코드가 됩니다.
이건 극단적으로 지나친 단순화인데, 질문하신 맥락과 추상 수준에서 상황을 이해하기에는 괜찮은 유비라고 생각합니다.
https://kldp.org/node/158902
https://kldp.org/node/158902
이 링크의 문답이 참고가 될 것 같습니다.
좋은 하루 되세요!
...
C/C++이 어떻게 실행되는지를 알기 위해 어셈블리를 보는 것은... 초심자 단계에서는 별로 좋은 생각이 아닐 것 같습니다.
기본적으로 요즘 컴파일러의 개념은 "xxx라는 문장이 있으면 어셈블리의 yyy로 바꾼다"가 아닙니다. 전체 프로그램을 통짜로 놓고 "바깥에서 보기에 *이 프로그램과 동일한* 동작을 하기만 하면 된다"라는 전제 하에 내부 구조를 멋대로 재배열하고 바꿔 버립니다. 특정한 컨디션이 에러라서 일어날 수 없다는 확신을 하면 (프로그래머가 그렇게 생각하든 말든) 해당 부분의 코드를 통째로 날려 버리기도 합니다.
"이런 코드는 어떻게 동작하나요?"를 알고 싶으면 해당 부분에 printf든 cout이든 넣어서 변수의 값을 직접 보는 게 훨씬 이해하기 쉽습니다.
댓글 달기