如何保护数组定义,防止使用非零值进行不完整的初始化

How to protect an array definition againt incomplete initialization with non-zero values?

本文关键字:非零值 初始化 保护 何保护 数组 定义      更新时间:2023-10-16

我有一个全局数组,它由枚举的值索引,其中有一个元素表示值的数量。数组必须用一个特殊值初始化,不幸的是这个值不是0。

enum {
  A, B, C, COUNT
};
extern const int arr[COUNT];

在。cpp文件中:

const int arr[COUNT] = { -1, -1, -1 };

枚举偶尔会被更改:添加新值,删除一些值。我刚刚修复的代码中的错误是初始化值的数量不足,这导致数组的其余部分用零初始化。我想对这种错误采取保护措施。

问题是要么保证arr总是用特殊值(示例中的-1)完全初始化,要么中断编译以引起开发人员的注意,因此可以手动更新数组。

最新的c++标准不可用(旧的ms编译器和一些私有垃圾)。在某种程度上,可以使用模板。STL和Boost是强烈禁止的(不要问),但我不介意复制或重新实现所需的部分。

如果结果证明是不可能的,我将不得不考虑将特殊值更改为0,但我想避免这种情况:特殊值(-1)可能有点太特殊了,并且在其余代码中隐式编码。

我想避免DSL和代码生成:主要构建系统在ms windows上卡住了,在那里生成任何东西都是主要的PITA。

我能想到的最好的解决方案是将arr[COUNT]替换为arr[],然后编写一个模板来断言sizeof(arr) / sizeof(int) == COUNT。这并不能确保它被初始化为-1,但它可以确保您已经用正确的元素数量显式初始化了数组。

c++ 11的static_assert会更好,或者Boost的宏版本,但如果你没有可用的,你必须自己想出一些东西。

这很简单。

enum {
  A, B, C, COUNT
};
extern const int (&arr)[COUNT];
const int (&arr)[COUNT] = (int[]){ -1, -1, -1};
int main() {
   arr[C];
}

乍一看,这似乎会产生开销,但是当您仔细检查它时,它只是为编译器关心的同一个变量生成两个名称。所以没有开销。

这里它是工作的:http://ideone.com/Zg32zH,下面是错误情况下发生的情况:http://ideone.com/yq5zt3

prog.cpp:6:27:错误:const int (&)[3]类型引用的初始化无效

const int[2]类型表达式

对于某些编译器,您可能需要命名临时

const int arr_init[] = { -1, -1, -1};
const int (&arr)[COUNT] = arr_init;
<标题> 更新

我被告知第一个=(int[]){-1,-1,-1}版本是一个编译器扩展,所以第二个=arr_init;版本是首选。

回答我自己的问题:虽然直接为数组提供适当数量的初始化式似乎是不可能的,但测试初始化式列表以获得适当数量的初始化式确实很容易:

#define INITIALIZERS -1, -1, -1,
struct check {
  check() {
    const char arr[] = {INITIALIZERS};
    typedef char t[sizeof(arr) == COUNT ? 1: -1];
  }
};
const int arr[COUNT] = { INITIALIZERS };

感谢@dauphic使用变量数组来计算值的想法。

预处理器库可能会提供一些有用的东西,但我怀疑您是否会被允许使用它,并且从Boost源中提取它可能会变得笨拙。

这个类似的问题有一个看起来很有用的答案:技巧:使用宏填充数组值(代码生成)

我能得到的最接近初始化而不是检查的方法是使用对数组的const引用,然后在全局对象中初始化该数组。它仍然是运行时初始化,但我知道你如何使用它,所以这可能足够好。

#include <cstring>
enum {A, B, C, COUNT};
namespace {
    class ArrayHolder {
        public:
            int array[COUNT];  // internal array
            ArrayHolder () {
                // initialize to all -1s
                memset(this->array, -1, sizeof(this->array));
            }
    };
    const ArrayHolder array_holder; // static global container for the array
}

const int (&arr)[COUNT] = array_holder.array;  // reference to array initailized
                                               // by ArrayHolder constructor

你仍然可以像以前一样使用sizeof:

for (size_t i=0; i < sizeof(arr)/sizeof(arr[0]); ++i) {
        // do something with arr[i]
}

编辑如果运行时初始化永远不能依赖,你应该检查asm中的实现细节,因为即使使用初始化器声明arr的值,在运行时初始化之前可能仍然不知道

const int arr[1] = {5};
int main() {
    int local_array[arr[0]]; // use arr value as length
    return 0;
}

g++ -pedantic编译给出警告:

 warning: ISO C++ forbids variable length array ‘local_array’ [-Wvla]

另一个编译失败的例子:

const int arr1[1] = {5};
int arr2[arr1[0]];

error: array bound is not an integer constant before ']' token
至于使用数组值作为全局构造函数的参数,这里的两个构造函数调用都是可以的:
// [...ArrayHolder definition here...]
class IntegerWrapper{
    public:
        int value;
        IntegerWrapper(int i) : value(i) {}
};

const int (&arr)[COUNT] = array_holder.array;
const int arr1[1] = {5};
IntegerWrapper iw1(arr1[0]); //using = {5}
IntegerWrapper iw2(arr[0]);  //using const reference

此外,跨不同源文件的全局变量的初始化顺序没有定义,您不能保证arr = {-1, -1, -1};直到运行时才会发生。如果编译器正在优化初始化,那么您依赖的是实现,而不是标准。

这里我真正想强调的一点是:int arr[COUNT] = {-1, -1, -1};仍然是运行时初始化,除非它可以被优化出来。你可以依赖它是常量的唯一方法是使用c++ 11的constexpr,但你没有可用的