如何将数组的所有成员初始化为相同的值?

1160

我在 C 中有一个大型数组(如果这有区别的话,不是 C++)。我希望将所有成员初始化为相同的值。

我记得曾经知道一种简单的方法来做到这一点。虽然我可以在我的情况下使用 memset(),但是否有一种内置于 C 语法中的方法来实现这一点呢?


25
迄今为止,没有任何答案提到在C99及以上版本中可行的指定初始化符号。例如:enum { HYDROGEN = 1, HELIUM = 2, CARBON = 6, NEON = 10, … };struct element { char name[15]; char symbol[3]; } elements[] = { [NEON] = { "Neon", "Ne" }, [HELIUM] = { "Helium", "He" }, [HYDROGEN] = { "Hydrogen", "H" }, [CARBON] = { "Carbon", "C" }, … };。如果删除省略号,这些片段可以在C99或C11下编译通过。 - Jonathan Leffler
实际上,abelenky的答案使用了指定初始化器,但并不是完全形成的初始化代码。 - Rob11311
memset()可以帮助,但取决于数值。 - Nick
2
memset()特定讨论:https://dev59.com/qmw05IYBdhLWcg3wfx80 我认为它只适用于0。 - Ciro Santilli OurBigBook.com
@Nick memset() 只适用于值为 0UINT_MAX 和元素大小为 char 的数组。 - user16217248
1
@user16217248 也适用于“循环”数字,例如0x1212、0x0303等。;) 但是是的,大多数情况下您需要零。 - Nick
27个回答

1483

除非该值为0(在这种情况下,您可以省略初始化程序的某些部分,相应的元素将被初始化为0),否则没有简单的方法。

但是不要忽视显而易见的解决方案:

int myArray[10] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };

具有缺失值的元素将被初始化为0:

int myArray[10] = { 1, 2 }; // initialize to 1,2,0,0,0...

因此,这将使所有元素初始化为0:

int myArray[10] = { 0 }; // all elements 0

C++中,空初始化列表也会将每个元素初始化为0。在C23之前的C语言中不允许这样做:

在C++中,空的初始化列表将会默认将每个元素初始化为0。而在C语言中,空的初始化列表则是非法的,直到C23版本才支持。

int myArray[10] = {}; // all elements 0 in C++ and C23

请记住,如果未指定初始化程序,则具有静态存储期的对象将初始化为0:

static int myArray[10]; // all elements 0

而且“0”并不一定意味着“所有位都是零”,因此使用上述方法比memset()更好、更具可移植性。(浮点数值将被初始化为+0,指针为null值等)


63
查看C99标准的第6.7.8节“初始化”,似乎不允许使用空初始化列表。 - Jonathan Leffler
11
C99有很多对于结构体和数组初始化的好特性。但它缺少一种特性(但是Fortran IV在1966年就有了),这种特性可以让你为一个数组重复使用同一个特定的初始值。 - Jonathan Leffler
2
我认为我还没有看到过这个:int myArray[] = {[3] = 123, [7] = 23}; 这将给你 {0, 0, 0, 123, 0, 0, 0, 23} - akofink
2
@akofink:请看下面qrdl的回答。这是GCC的扩展功能。 - aib
2
@Fratink 请参阅§6.7.8.21:“如果在大括号括起来的列表中的初始化程序比聚合体中的元素或成员少,或者用于初始化已知大小的数组的字符串文字中的字符比数组中的元素少,则聚合体的其余部分应隐式初始化与具有静态存储期的对象相同。” - aib
显示剩余9条评论

474

23
这种语法会导致编译后的二进制文件大小大幅增加。当 N = 65536 时(而不是 1024),我的二进制文件大小从 15 KB 跃升至 270 KB!! - Cetin Sert
78
编译器需要将65536个整数添加到静态数据中,这是256K的大小增加,正好对应你观察到的增加。 - qrdl
21
为什么?这是标准的编译器行为,与指定初始化器无关。如果你静态地初始化了65536个整数,例如int foo1 = 1, foo2 = 1, ..., foo65536 = 1;,你会得到相同的大小增加。 - qrdl
36
更好的是:"int array[] = {[0 ... 1023] = 5}",数组的大小将自动设置为1024,更容易且更安全地进行修改。 - Francois
5
对于一个二维数组,可以这样声明:bool array[][COLS] = { [0...ROWS-1][0...COLS-1] = true},但我不确定这比完整形式更易读。 - user67416
显示剩余9条评论

201

为了在静态初始化大型数组时使用相同的值,而不需要多次复制和粘贴,可以使用宏:

对于静态初始化大型数组并使用相同值,可以使用宏来简化操作:

#define VAL_1X     42
#define VAL_2X     VAL_1X,  VAL_1X
#define VAL_4X     VAL_2X,  VAL_2X
#define VAL_8X     VAL_4X,  VAL_4X
#define VAL_16X    VAL_8X,  VAL_8X
#define VAL_32X    VAL_16X, VAL_16X
#define VAL_64X    VAL_32X, VAL_32X

int myArray[53] = { VAL_32X, VAL_16X, VAL_4X, VAL_1X };

如果你需要更改值,那么你只需要在一个地方进行替换。

编辑:可能有用的扩展

(由Jonathan Leffler提供)

你可以轻松地将此通用化:

#define VAL_1(X) X
#define VAL_2(X) VAL_1(X), VAL_1(X)
/* etc. */

可以使用以下方式创建变体:

#define STRUCTVAL_1(...) { __VA_ARGS__ }
#define STRUCTVAL_2(...) STRUCTVAL_1(__VA_ARGS__), STRUCTVAL_1(__VA_ARGS__)
/*etc */ 

可以与结构体或复合数组一起使用的。

#define STRUCTVAL_48(...) STRUCTVAL_32(__VA_ARGS__), STRUCTVAL_16(__VA_ARGS__)

struct Pair { char key[16]; char val[32]; };
struct Pair p_data[] = { STRUCTVAL_48("Key", "Value") };
int a_data[][4] = { STRUCTVAL_48(12, 19, 23, 37) };

宏名称可以商议。


14
只有在极端情况下才会考虑这种方式,使用memset更为简洁明了。 - u0b34a0f6ae
52
如果数据必须能够 ROM 存储,则不能使用 memset。 - Prof. Falken
11
预处理器将从 #define 生成代码。随着数组维度的增大,可执行文件的大小将会增加。但这个想法是肯定会有好处的 ;) - Leonid
9
在旧电脑和许多嵌入式系统上,代码最终会被放置在EPROM或ROM中。在嵌入式系统中,ROM-able也意味着“将代码放入Flash”,因为它具有相同的含义,即不能在运行时编写内存。也就是说,不能使用memset或其他指令来更新或更改内存。但是,常量可以在程序启动之前被表达并闪存或ROM化。 - Prof. Falken
4
请注意,即使 VAL_1X 不是单个整数而是一个列表,您仍然可以使用此方法。像Amigable所说的那样,这也是嵌入式系统中定义EEPROM或Flash存储器初始值的方法。在这两种情况下,您都不能使用 memset() - Martin Scharrer
显示剩余8条评论

66

如果您想确保数组的每个成员都被明确初始化,只需在声明中省略维度:

int myArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

编译器将从初始化列表中推断出数组的维度。不幸的是,对于多维数组,只有最外层的维度可以省略:

int myPoints[][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9} };

没问题,但是

int myPoints[][] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9} };

不是。


这是正确的吗? int myPoints[10][] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9} }; - Praveen Gowda I V
12
不行。你省略了最内层的维度,这是不允许的。这将导致编译器出错。 - Frank Szczerba
4
C99 引入了初始化器和长度推断的功能。 - Palec
3
@Palec说:不是 —— 长度推断自从标准C之前的时代(自K&R第一版出版以来,可能还有一段时间)。指定的初始化在C99中是新的,但这并不使用指定的初始化。 - Jonathan Leffler

60

我看到一些代码使用了这种语法:

char* array[] = 
{
    [0] = "Hello",
    [1] = "World"
};   

如果你正在创建一个将枚举用作索引的数组,那么它就特别有用:

enum
{
    ERR_OK,
    ERR_FAIL,
    ERR_MEMORY
};

#define _ITEM(x) [x] = #x

char* array[] = 
{
    _ITEM(ERR_OK),
    _ITEM(ERR_FAIL),
    _ITEM(ERR_MEMORY)
};   

即使你把枚举值按照不同的顺序编写,这种方法也可以使它们保持有序。

关于这种技术的更多信息可以在这里这里找到。


9
这是C99的初始化语法,其他回答已经涵盖了这一点。你可以将声明有用地改为char const *array[] = {...};或者甚至是char const *const array[] = {...};,不是吗? - Jonathan Leffler

23
int i;
for (i = 0; i < ARRAY_SIZE; ++i)
{
  myArray[i] = VALUE;
}

我认为这比之前更好。

int myArray[10] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5...

防止数组大小发生改变。


20
如果要用memset把int数组初始化为大于255的某个值,只能先将数组转换成字节大小。 - Matt
24
@Benson:如果在sizeof(int) > sizeof(char)的平台上,你不能用memset替换上面的代码。试试看。 - ChrisWue
1
为什么要在循环外声明 i - user16217248
这两者并不相等。一个在编译时初始化,另一个在运行时初始化。 - user229044

12

您可以像上面详细介绍的那样完成整个静态初始化程序,但是如果数组大小发生变化(如果您未添加适当的额外初始化程序,则数组扩大时会出现垃圾数据),这可能会让人沮丧。

memset方法会在运行时给你带来一些负担,但正确使用不会对代码大小产生影响,但仍然无法免除数组大小变化所带来的问题。我建议在数组大小超过几十个元素时,几乎所有情况下都使用此解决方案。

如果真的很重要,必须静态声明数组,那么我会编写一个编写程序的程序,并将其作为构建过程的一部分。


能否请您在memset初始化数组的用法上加入一些示例? - Sopalajo de Arrierez

9

我知道原问题明确提到了C而不是C++,但如果你(像我一样)在这里寻找C++数组的解决方案,这里有一个巧妙的方法:

如果你的编译器支持fold表达式,你可以使用模板魔法和std::index_sequence生成一个初始化列表,并用你想要的值填充它。你甚至可以把它变成constexpr并感觉像个大佬:

#include <array>

/// [3]
/// This functions's only purpose is to ignore the index given as the second
/// template argument and to always produce the value passed in.
template<class T, size_t /*ignored*/>
constexpr T identity_func(const T& value) {
    return value;
}

/// [2]
/// At this point, we have a list of indices that we can unfold
/// into an initializer list using the `identity_func` above.
template<class T, size_t... Indices>
constexpr std::array<T, sizeof...(Indices)>
make_array_of_impl(const T& value, std::index_sequence<Indices...>) {
    return {identity_func<T, Indices>(value)...};
}

/// [1]
/// This is the user-facing function.
/// The template arguments are swapped compared to the order used
/// for std::array, this way we can let the compiler infer the type
/// from the given value but still define it explicitly if we want to.
template<size_t Size, class T>
constexpr std::array<T, Size> 
make_array_of(const T& value) {
    using Indices = std::make_index_sequence<Size>;
    return make_array_of_impl(value, Indices{});
}

// std::array<int, 4>{42, 42, 42, 42}
constexpr auto test_array = make_array_of<4/*, int*/>(42);
static_assert(test_array[0] == 42);
static_assert(test_array[1] == 42);
static_assert(test_array[2] == 42);
static_assert(test_array[3] == 42);
// static_assert(test_array[4] == 42); out of bounds

您可以在Wandbox上查看代码工作情况。 (链接)

9
这里还有另一种方法:
static void
unhandled_interrupt(struct trap_frame *frame, int irq, void *arg)
{
    //this code intentionally left blank
}

static struct irqtbl_s vector_tbl[XCHAL_NUM_INTERRUPTS] = {
    [0 ... XCHAL_NUM_INTERRUPTS-1] {unhandled_interrupt, NULL},
};

参见:

C 扩展

指定初始化

那么问题来了:什么时候可以使用 C 扩展?

上面的代码示例是在嵌入式系统中,永远不会被其他编译器看到。


8
一种略带幽默的回答:写出这个语句。
array = initial_value

在你喜欢的数组可处理语言中(我喜欢Fortran,但也有其他语言),并将其链接到你的C代码。你可能想要将其包装成一个外部函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接