개요
칩 검증 코드를 수정하기 위해 간단한 루틴을 작성하고 있던 도중 커널에서 제공하는 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
매크로는 함수 내에 지역 변수로서 정의한 경우에는 사용할 수 있지만 함수로 넘겨서 사용하는 경우에는 사용이 불가능하다. 배열의 크기를 반드시 명시적으로 전달해줘야 전달받은 루틴에서 정상적으로 그 크기를 다룰 수 있다.
기초적인 내용인데도 불구하고 막상 문제에 닥치니 제대로 알기가 어렵다. 아직 한참 멀었다.