【问题标题】:Ensuring array is filled to size at compile time确保数组在编译时填充到大小
【发布时间】:2016-01-08 03:46:24
【问题描述】:

如何确保在编译时将 COLOR_NAMES 填充到正确的大小?如果添加了新颜色,比如 COLOR_4(因此 N_COLORS 会自动递增),编译器会告诉我 COLOR_NAMES 未填充到大小。

我在网上找到的大多数答案都是针对运行时的,而不是编译时的。

这是用于 C 风格的表示法(不使用 STL 和其他库)。

enum Colors
{
   COLOR_1,
   COLOR_2,
   COLOR_3,
   N_COLORS;
};

const char* COLOR_NAMES[N_COLORS] =
{
   /* COLOR_1 */ "Color1",
   /* COLOR_2 */ "Color2",
   /* COLOR_3 */ "Color3"
};

const char* Blah()
{
   Colors color;
   ...
   printf("%s blah blah\n", COLOR_NAMES(color));
}

【问题讨论】:

标签: c arrays


【解决方案1】:

对于此类数组和相应枚举,将“枚举大小成员”N_COLORS 与数组中的项目数进行比较是标准做法。

要获取数组中的项目数,只需将数组大小除以一个数组成员的大小即可。

因此:

_Static_assert(sizeof(COLOR_NAMES)/sizeof(*COLOR_NAMES) == N_COLORS, 
               "Array item missing!");

编辑:

哦,顺便说一句,为了有意义,数组声明必须是 const char* COLOR_NAMES[] = 否则您将无法判断数组初始化列表中是否缺少初始化程序。

【讨论】:

    【解决方案2】:

    理想的情况是能够使用预处理器中的 sizeof。但我们不能,因为 sizeof 是由编译器评估的。

    有很多方法可以绕过它,但这里有一个非常简单和便携的方法:

    const char* COLOR_NAMES[] = {
       /* COLOR_1 */ "Color1",
       /* COLOR_2 */ "Color2",
       /* COLOR_3 */ "Color3"
    };
    
    typedef char CHECK_COLOR_NAMES[sizeof(COLOR_NAMES) / sizeof(COLOR_NAMES[0]) == N_COLORS ? 1 : -1];
    

    如果测试失败,你将尝试定义一个大小为 -1 的数组,这将导致编译错误。

    编辑:然后我们使用 typedef 来避免实际创建一个我们不会使用的变量(Lundin 的话)

    【讨论】:

    • 在创建“穷人的静态断言”时,最好使用typedef。这样,您实际上不会为您的无意义数组分配任何内存。更好的是:获得一个不超过 5 年的 C 编译器,您将获得对静态断言的语言支持。
    • 用一秒钟的旧编译器进行静态断言的可移植方式是什么?谢谢。
    • 我认为仅在 C11 中存在静态断言就是升级到该标准的原因。旧的“无法在第 666 行声明大小为 -1 的数组”编译时断言错误不是很有描述性 :)
    • 嗯,好的,谢谢!我一直对标准静态断言保持沉默,因为 VS(包括 VS2015)只是不知道 _Static_assert,并且通常只是忽略 C11。但是我刚刚了解到 C11 还在 assert.h 中定义了 static_assert。在 VS 中,这是一个关键字,而不是 assert.h 中的宏:P。但这无关紧要。所以我认为当今结合理论和实践的最佳方式是包含 并使用 static_assert,以获得最大的可移植性。再次感谢 Lundin :)。
    • 这是因为VS主要是一个C++编译器,而static_assert是C++中的一个关键字。除了 C11 中的 _Static assert 关键字,它还包含 static_assert 作为宏,以实现 C++ 兼容性。 C模式下的VS2015之所以看不懂_Static_assert,是因为尽管名称中有2015,但它仍然是90年代中期的旧垃圾编译器。对于纯 C 编程,我会完全避免使用该编译器。
    【解决方案3】:

    使用 constepxr,您可以使用:

    constexpr static const char* COLOR_NAMES[N_COLORS] =
    {
       /* COLOR_1 */ "Color1",
       /* COLOR_2 */ "Color2",
       /* COLOR_3 */ "Color3"
    };
    
    static_assert(COLOR_NAMES[N_COLORS-1] !=0);
    

    如果要使用局部变量,可以使用变体

    constexpr variant<const char*> COLOR_NAMES[N_COLORS] =
    {
       /* COLOR_1 */ "Color1",
       /* COLOR_2 */ "Color2",
       /* COLOR_3 */ "Color3"
    };
    
    static_assert(get<0>(COLOR_NAMES[N_COLORS-1]) !=nullptr);
    

    【讨论】:

    • 如果 COLOR_NAMES 之外的内存空间一开始就不为零怎么办?
    • @Ryuu 根据这个链接:stackoverflow.com/a/7760316/440403,如果 T 不是一个类(T array[]),那么它的元素不会被初始化。但根据stackoverflow.com/a/20916572/440403,静态变量将使用默认值初始化
    • @Ryuu 用于局部变量,你可以将 constexpr 与 variant 一起使用,因为 variant 保证第一个元素将被初始化为默认值。我从这里得到了这个想法:bfilipek.com/2018/06/variant.html
    • 很酷的文章。谢谢!
    猜你喜欢
    • 2013-11-15
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 2020-02-22
    • 2014-11-28
    • 1970-01-01
    • 2015-07-20
    • 1970-01-01
    相关资源
    最近更新 更多