sizeof 获取 extern 数组长度

sizeof是获取数组元素个数的常用运算符,然而前几天使用时发现,对于extern类型的数组,sizeof的使用上是有些需要考虑的问题的。

假设系统中有3个文件:

file1.c:

1
int array[] = {1, 2, 3};

header1.h:

1
extern int array[];

main.c:

1
2
3
4
5
6
7
#include "header1.h"

void fun()
{
// This is WRONG!
size_t elements_in_array = sizeof(array) / sizeof(int);
}

main.c中期望通过sizeof运算符获取array中元素个数,然而这么做是错误的,编译时无法通过,错误提示类似incomplete type not allowed这类。

造成这一问题的原因在于,sizeof是在编译时计算的,而C/C++的编译是以文件为基本单位的。在编译main.c文件时,编译器是不可能知道定义在file1.c文件中array数组具体信息的,只根据header1.h文件中的声明是无法确定array的具体大小的,因此,就算某些编译器编译时不报错,得到的结果也是不正确的。

分析清楚原因后来看下解决方案,基本解决方法有4种:

  1. 避免使用匿名长度的数组声明,使用宏定义预先确定数组大小;
  2. 定义一个辅助变量用于保存数组大小信息,将其定义赋值放在定义array数组的同一个文件中;
  3. 使用特殊元素表示数组结束,就像字符串结尾的'\0'一样,这样就可以在运行阶段动态确定数组大小;
  4. 将数组的定义放到使用它的源文件中。

这几种方法都有其缺点:

  1. 使用sizeof就是不想固定数组长度,因为使用宏定义固定数组长度不够灵活,要是想添加数组元素也要同时修改宏定义,否则尽管编译不会报错,然而运行时新添加的元素其实是无效的,这会导致将来维护时一些潜在Bug发生的可能性增加;
  2. 需要一个额外的存储空间,且由于这是一个变量,每次使用数组长度时都需要访问内存,编译器也无法对数组长度作出任何假设,进而影响编译优化,理论上说这可能会导致运行时一些微小的效率损失;
  3. 需要修改上层逻辑,缺乏通用性;
  4. 大部分情况下,使用非static全局变量的原因就是多个源文件需要使用这个变量,这时显然无法做到这一点,多次重复定义链接时会出错的。

实际使用中,需要根据具体问题具体分析采用哪种方法最恰当,一般而言不经常变化的数组就使用宏定义确定其大小,会经常变化的第2种方法最常用,此时还可以用一些宏定义简化编程,以上代码可修改为:

file1.c:

1
2
3
4
5
#include "header1.h"

int array[] = {1, 2, 3};

ELEMENTS_IN_DEF(array)

header1.h:

1
2
3
4
5
6
#define  ELEMENTS_IN(array)            __elements_in_##array
#define ELEMENTS_IN_DEF(array) size_t __elements_in_##array = sizeof(array) / sizeof(array[0]);
#define ELEMENTS_IN_DECLARE(array) extern size_t __elements_in_##array;

extern int array[];
ELEMENTS_IN_DECLARE(array)

main.c:

1
2
3
4
5
6
#include "header1.h"

void fun()
{
size_t elements_in_array = ELEMENTS_IN(array);
}

参考资料:

comp.lang.c FAQ list · Question 1.24

C: How to determine sizeof(array) / sizeof(struct) for external array?

sizeof extern数组