상위 문서: Assembly
개요
해당 문서에서는 1차원 배열(array)와, 다차원 배열, 그리고 포인터 배열의 특성에 대해 다룬다. 또한 구조체(struct)를 선언할 때, 구조체가 메모리에 어떻게 배치되는지와 정렬 및 패딩(padding) 규칙이 구조체에 어떤 영향을 미치는지에 대해 다룬다.
Array
C에서는 기본적으로 아래와 같이 배열을 선언한다:
T A[N];
T는 배열의 원소(element)의 자료형, N는 배열에 최대로 저장될 수 있는 원소의 수에 해당한다. 위 명령어를 통해 총 N * sizeof(T) 바이트의 연속적인 메모리 할당이 이뤄진다. 예를 들어, char *p[3];와 같은 명령어는 포인터 자료형의 크기가 8바이트이므로, 총 24바이트의 공간이 할당된다. 이는 figure 1이 잘 보여준다.
Array Access
기본적으로 배열 이름 A는 배열의 첫 원소의 시작 주소를 지정하는 포인터처럼 작동한다. 예를 들어, 배열 val이 figure 2와 같이 선언되었을 때, 배열 이름 val에 대한 산술 연산은 아래와 같이 작동한다.
| Expression | Type | Value |
|---|---|---|
| val | int* | x |
&val[1] or val + 1
|
int* | x + 1 * 4 |
val[1] or *(val + 1)
|
int | 5 |
&val[i] or val + i
|
int* | x + i * 4 |
아래는 배열의 원소에 접근하는 C언어 기반의 함수와, 동일한 작업을 수행하는 어셈블리 코드이다.
int get_elem(int* arr, long idx) {
return arr[idx];
}
get_elem:
mov (%rdi,%rsi,4), %eax
ret
위 어셈블리 코드에서 %rdi는 배열의 시작 주소를 의미하며, %rsi는 인덱스를 의미한다. 따라서, arr[idx]는 %rdi + 4 * %rsi에 위치한 원소이다.
Array vs. Pointer in C
배열과 포인터는 밀접한 관계가 있지만, 동일하지 않다. char str1[32];과 같이 배열 이름은 포인터처럼 사용될 수 있지만, 진짜 포인터 변수는 아니다. 이는 아래의 예시 코드를 통해서 알아볼 수 있다.
char str1[32];
char str2[64];
char *p = str1;
printf("%p vs. %p\n", str1, p);
printf("sizeof(str1) = %ld\n", sizeof(str1)); // 배열의 크기 = 32
printf("sizeof(p) = %ld\n", sizeof(p)); //포인터 크기 = 8
p = str2; //char* 포인터에 배열 이름 할당은 괜찮음
str2 = p; //이는 컴파일 오류를 야기
위의 코드에서는 마지막 줄이 컴파일 오류를 야기한다. 이는 배열 이름은 사실상 상수와 같이 동작하므로, 재할당이 불가하기 때문이다. 하지만 그 외에는 사실상 포인터와 같이 동작하므로, 서로 호환되어 사용되는 것을 보여준다.