a[i]과 *a의 차이
sum1이라는 함수가 있다. a와 b 2개의 수가 주어졌을 때 a부터 b까지의 합을 계산하여 반환한다.
int sum1(int a, int b) {
int sum = 0;
for (int i = a; i <= b; i++)
sum += i;
return sum;
}
sum1에서 루프를 도는 코드는 다음과 같이 컴파일이 된다(-O2 옵션).
_loop:
01 f8 add eax,edi
83 c7 01 add edi,0x1 ; loop variable 값을 1 증가시킨다.
39 f7 cmp edi,esi
75 f7 jne _loop
sum5라는 함수가 있다. 위의 sum1 함수와 비슷하지지만 루프 안에서 i를 사용하지 않고 “i * 5”를 사용한다.
int sum5(int a, int b) {
int sum = 0;
for (int i = a; i <= b; i++)
sum += i * 5; // i 값의 5배를 더한다.
return sum;
}
sum5에서 루프를 도는 코드는 다음과 같다(-O2 옵션). 루프 안에서 “i * 5” 연산을 하는 것이 아니라(mul이라는 op code의 사용을 하지 않음) loop variable 자체에 5를 더해서 처리해 버린다.
_loop:
41 01 c0 add r8d,eax ; mul op code를 사용하지 않고
83 c0 05 add eax,0x5 ; loop variable 값을 5 증가시킨다.
39 d0 cmp eax,edx
75 f6 jne _loop:
루프를 돌면서 배열(혹은 포인터)에 있는 값들을 참조하기 위해서는 “a[i]” 혹은 “*a” 와 같은 코드를 사용할 수 있다. 아래 2개의 함수를 보자.
int sum_index(int a[], int n) {
int sum = 0;
for (int i = 0; i < n; i++)
sum += a[i]; // 요거!!!
return sum;
}
int sum_pointer(int* a, int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += *a; // 요거!!!
a++;
}
return sum;
}
sum_index 및 sum_pointer 내부에 있는 코드는 Debug 모드에서 다음과 같이 컴파일된다.
sum += a[i];
8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
48 98 cdqe
48 8d 14 85 00 00 00 00 lea rdx,[rax*4+0x0] ; 곱하기 4(int의 크기)가 이루어진다.
48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18]
48 01 d0 add rax,rdx
8b 00 mov eax,DWORD PTR [rax]
01 45 fc add DWORD PTR [rbp-0x4],eax
sum += *a;
48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18]
8b 00 mov eax,DWORD PTR [rax] ; 근냥 포인터. 곱하기가 없다.
01 45 fc add DWORD PTR [rbp-0x4],eax
그런데 Release 모드(-O2)에서는 sum_index와 sum_pointer 함수의 루프를 도는 코드는 비슷하게 컴파일된다(인덱스 값의 곱하기가 없이).
_loop:
sum += a[i];
03 07 add eax,DWORD PTR [rdi]
for (int i = 0; i < n; i++)
48 83 c7 04 add rdi,0x4 ; 1이 아닌 4를 더한다.
48 39 d7 cmp rdi,rdx
75 f5 jne _loop
_loop:
sum += *a;
03 07 add eax,DWORD PTR [rdi]
a++; ; 포인터 변수 값에 4를 더한다.
48 83 c7 04 add rdi,0x4
for (int i = 0; i < n; i++) {
48 39 d7 cmp rdi,rdx
75 f5 jne _loop
결론 : 일반적으로 “a[i]”를 사용하는 것보다 “*a” 를 사용하는 것이 속도면에서 좋다고 알려져 있으나, 최적화를 거치게 되면 코드의 결과가 비슷해 지는 것을 알 수 있다.