디스어셈블리 코드로 살펴보는 전치 연산과 후치 연산
디스어셈블리 코드를 통해 C++의 전치 연산과 후치 연산의 동작 원리를 살펴 본다
오랜만에 포스팅 하면서..
지식in을 돌아다니다 보니 의외로 전치,후치연산과 관련된 질문들이 많이 올라와 있어
짧게나마 전치,후치연산에 대한 글을 포스팅 해보려 한다.
아주 기초적인 내용이지만…
어셈 코드를 인용해 이 전치연산과 후치연산이 내부적으로 어떻게 작동 되어지는지 살펴봄으로써,
이제 막 C나 C++을 배우기 시작하신 분들 및 정리가 필요하신 분들께 도움이 되었으면 좋겠다.
1. 전치연산과 후치연산
c/c++ 코드 상에서 정수형 변수 i의 값이 10 일때,
전치연산 “++i”의 결과 값이나 후치연산 “i++”의 결과 값 모두 i=10 으로 동일 하다.
하지만, 이 결과값을 다른 변수에 넣고 그 변수를 다른 연산에 활용해야 하는 경우
위의 연산의 결과를 동일하게 취급한다면 의도하지 않은 옳지 못한 값을 얻게 된다.
예를 들어,
int j =0, i = 10 일때,
“j = ++i” 와 “j = i++” 연산의 결과 “j의 최종값”은 “11”,”10”으로 서로 다른데도 불구하고
i의 값과 j의 값은 모두 10으로 같을 것이다 라는 잘못된 결론에 이르게 될 수도 있는 것이다.
짧게 정리해 보자면..
전치연산 :
“j = ++i” 는 “변수 i의 값”이 1증가된 뒤에, “변수 j”에 “앞에서 1증가된 변수 i의 값이”이 대입 된다.후치연산 :
“j = i++” 는 “변수 i의 현재값”이 “변수 j”에 대입된 뒤에 “변수 i의 값”이 1 증가 된다.
2. C++ 소스코드
실행 결과 값은 각 행 우측에 주석 처리 해놓았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int i = 10;
int j = 0;
cout << "result value of i : " << i << endl; // i = 10
cout << "result value of j : " << j << endl; // j = 0
cout << endl;
j = ++i; // i의 전치연산 값을 j에 넣는다.
cout << "result value of ++i : " << i << endl; // i = 11
cout << "result value of j : " << j << endl; // j = 11
cout << endl;
j = i++; // i의 후치연산 값을 j에 넣는다.
cout << "result value of i++ : " << i << endl; // i = 12
cout << "result value of j : " << j << endl; // j = 11
cout << endl;
3. 어셈블리 코드로 살펴보는 전치연산과 후치연산
그럼 이제 전치연산과 후치연산을 어셈블리 코드를 통해 살펴보자.
⚠️ 위 코드를 디버깅(f10)한 뒤에, 디스어셈블 코드로 보기를 선택하면 컴파일된 2진 코드를 디스어셈블하여 어셈블리 코드로 표현된 아래와 같은 코드들을 볼 수 있다.
1) 전치 연산 어셈블리 코드
1
2
3
4
5
6
j = ++i;
01224607 mov eax,dword ptr [i]
0122460A add eax,1
0122460D mov dword ptr [i],eax
01224610 mov ecx,dword ptr [i]
01224613 mov dword ptr [j],ecx
mov eax,dword ptr [i]
메모리 상에서 i라는 변수 이름으로 대변되고 있는 주소상의 데이터 값 10을 더블워드(32비트) 타입으로 엑세스하여,
eax(확장-어큐뮬레이터) 레지스터로 전송.
-> cpu내의 eax 레지스터에 memory에 로드 되어 있는 변수 i의 값 10 이 전송 된다(eax == 10).
add eax,1
eax 레지스터 값에 1을 더하고, 이 결과 값을 다시 eax에 덮어 쓴다. -> eax(10)+1 == 11
mov dword ptr [i],eax
eax 레지스터에 있는 값을, 메모리 공간 i에 더블워드 타입으로 전송. -> 변수 i의 값이 11이 된다.
mov ecx,dword ptr [i]
mov dword ptr [j],ecx
-> ecx(확장 카운터) 레지스터를 경유하여 변수 i의 값을 변수 j에 전송.
결국, i와 j의 값 모두 11이 된다.
2) 후치연산 어셈블리 코드
1
2
3
4
5
6
j = i++;
012246B1 mov eax,dword ptr [i]
012246B4 mov dword ptr [j],eax
012246B7 mov ecx,dword ptr [i]
012246BA add ecx,1
012246BD mov dword ptr [i],ecx
mov eax,dword ptr [i]
변수 i에 있는 값을 더블워더 타입으로 엑세스하여 취한 뒤에 eax 레지스터로 전송 한다. -> 변수 i의 값과 eax 레지스터의 값은 현재 모두 10인 상태.
mov dword ptr [j],eaxeax 레지스터에 있는 값을 변수 j에 더블워드 타입으로 전송 한다.
-> 변수 i, eax 레지스터 값, 변수 j의 값 모두 10이 된다.
mov ecx,dword ptr [i]
변수 i에 있는 값 10을 ecx 레지스터로 전송 한다. -> ecx 레지스터의 값이 10이 된다.
add ecx,1
ecx 레지스터의 값 10에 1을 더해 준다. -> ecx의 값이 11이 된다.
mov dword ptr [i],ecx
ecx 레지스터의 값 11을 변수 i에 더블워드 타입으로 가져다 놓는다. -> 변수 i의 값이 11이 된다.
결국, i의 값은 11, j의 값은 10이 된다.
3) 결론
C언어에서 설명하고 있는대로 전치연산은 i의 값이 증가된 뒤에 j에도 할당 되어져 두 값 모두가 증가된 값을 가지게 되었고 후치연산은 i의 값이 먼저 j에 할당된 뒤에 i값 하나만 증가 되어 i값만 증가된 값을 가지게 되었다.
간략하게 간추려 보자면, 내부적으로 레지스터를 이용하여 어떤 변수(메모리)의 값을 먼저 다루느냐에 따라 전치연산과 후치연산이 구분되어진다고 볼 수 있다.