C ARRAY_SIZE 매크로와 포인터 기초

Mar 28, 2020    #array_size  

개요

칩 검증 코드를 수정하기 위해 간단한 루틴을 작성하고 있던 도중 커널에서 제공하는 ARRAY_SIZE 매크로가 의도한대로 동작하지 않아 살펴보기 시작했다. 그러던 도중 https://kldp.org/node/34268 과 같은 원인이라는 것을 알게되고 C 기초를 또 다시 한번 공부하게 되었다.

커널에서 사용하는 ARRAY_SIZE는 아래와 같이 구현되어 있다.

1#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
2
3// examples
4for (idx = 0; idx < ARRAY_SIZE(pArr); idx++) {
5    // do something
6})

본래 의도한대로라면, 배열의 크기만큼 for 구문을 반복해서 수행해야 하지만, pArr 자체가 함수의 인자로써 전달된 것이라면 얘기가 달라진다. 함수의 인자, 즉 포인터의 사이즈를 받게 되므로 ARRAY_SIZE(x)는 포인터 변수 자체의 크기를 첫 번째 요소의 크기로 나눈 것이 된다. 설명보다 직접 코드를 통해 증명해보자.

예제

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 5
 6void tc1(void) {
 7    int arr[] = {1, 2, 3};
 8    printf("ARRAY_SIZE = %ld\n", ARRAY_SIZE(arr));
 9}
10
11void tc2(int *pArr) {
12    printf("ARRAY_SIZE = %ld\n", ARRAY_SIZE(pArr));
13    printf("sizeof(x) = %d, sizeof(x[0]) = %d\n", sizeof(pArr), sizeof(pArr[0]));
14}
15
16void tc3(char *pArr) {
17    printf("ARRAY_SIZE = %ld\n", ARRAY_SIZE(pArr));
18    printf("sizeof(x) = %d, sizeof(x[0]) = %d\n", sizeof(pArr), sizeof(pArr[0]));
19}
20
21void tc4(unsigned long *pArr) {
22    printf("ARRAY_SIZE = %ld\n", ARRAY_SIZE(pArr));
23    printf("sizeof(x) = %d, sizeof(x[0]) = %d\n", sizeof(pArr), sizeof(pArr[0]));
24}
25
26int main(void) {
27    int arrInt[] = {4, 5, 6, 7};
28
29    printf("size of int = %d, size of long = %d\n", sizeof(int), sizeof(long));
30    printf("size of pointer value = %d\n", sizeof(arrInt[0]));
31
32    printf("[Test case 1]\n");
33    tc1();
34
35    printf("[Test case 2]\n");
36    tc2(arrInt);
37
38    printf("[Test case 3]\n");
39    tc3(arrInt);
40
41    printf("[Test case 4]\n");
42    tc4(arrInt);
43}

위 코드를 실행하면 아래와 같은 결과를 얻는다.

 1size of int = 4, size of long = 8
 2size of pointer value = 4
 3[Test case 1]
 4ARRAY_SIZE = 3
 5[Test case 2]
 6ARRAY_SIZE = 2
 7sizeof(x) = 8, sizeof(x[0]) = 4
 8[Test case 3]
 9ARRAY_SIZE = 8
10sizeof(x) = 8, sizeof(x[0]) = 1
11[Test case 4]
12ARRAY_SIZE = 1
13sizeof(x) = 8, sizeof(x[0]) = 8

함수의 파라미터로 정의되어 있는 포인터 변수들은 프로세서 아키텍처의 주소 크기만큼을 갖는다. 테스트는 64비트에서 이루어졌으므로 첫 번째 테스트케이스를 제외한 나머지에서 sizeof(x)는 모두 8(64비트)를 갖는다. 그리고 포인터의 타입에 따라 첫 번째 인자가 갖는 크기는 달라지게 되므로 결과값은 모두 달라지게 된다. 때문에 ARRAY_SIZE 매크로는 함수 내에 지역 변수로서 정의한 경우에는 사용할 수 있지만 함수로 넘겨서 사용하는 경우에는 사용이 불가능하다. 배열의 크기를 반드시 명시적으로 전달해줘야 전달받은 루틴에서 정상적으로 그 크기를 다룰 수 있다.

기초적인 내용인데도 불구하고 막상 문제에 닥치니 제대로 알기가 어렵다. 아직 한참 멀었다.