C Compile-Time使用常量数组断言

时间:2009-11-08 08:25:25

标签: c arrays assert static-assert

我有一个非常大的常量数组,在编译时初始化。

typedef enum {
VALUE_A, VALUE_B,...,VALUE_GGF
} VALUES;

const int arr[VALUE_GGF+1] = { VALUE_A, VALUE_B, ... ,VALUE_GGF};

我想验证数组是否已正确初始化,例如:

if (arr[VALUE_GGF] != VALUE_GGF) {
    printf("Error occurred. arr[VALUE_GGF]=%d\n", arr[VALUE_GGF]);
    exit(1);
}

我的问题是我想在编译时验证这一点。我在这个帖子中已经读过C中的编译时断言:C Compiler asserts。但是,那里提供的解决方案建议使用负值定义一个数组作为编译错误的大小:

#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file)
#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line, file) \
   typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];

并使用:

CASSERT(sizeof(struct foo) == 76, demo_c);

提供的解决方案对我来说不起作用,因为我需要验证我的常量数组值,C不允许使用常量数组值初始化数组:

int main() {
   const int i = 8;
   int b[i];         //OK in C++
   int b[arr[0]];    //C2057 Error in VS2005

周围有什么办法吗?其他一些编译时断言?

6 个答案:

答案 0 :(得分:3)

在下面的代码中,请参阅第6行和第9行中以固定长度声明的指针的额外分配。

如果没有为WORKDAYS枚举的所有值初始化2个数组,这将在编译时产生错误。 Gcc说:test.c:6:67:警告:从不兼容的指针类型初始化[默认启用]

想象一下,一位经理将SATURDAY添加到工作周枚举中。如果没有额外的检查,程序将编译,但运行时会因为分段违规而崩溃。

这种方法的缺点是它占用了一些额外的内存(我没有测试过它是否被编译器优化了)。 它也有点hackish,下一个人的代码中可能需要一些注释......

请注意,测试的数组不应声明数组大小。设置数组大小将确保您保留了数据,但不确保它包含有效的内容。

#include <stdio.h>

typedef enum { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, NOF_WORKDAYS_IN_WEEK } WORKDAYS;

const char * const workday_names[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" };
const char * const (*p_workday_name_test)[NOF_WORKDAYS_IN_WEEK] = &workday_names;

const int workday_efforts[] = { 12, 23, 40, 20, 5 };
const int (*p_workday_effort_test)[NOF_WORKDAYS_IN_WEEK] = &workday_efforts;

main ()
{
  WORKDAYS i;
  int total_effort = 0;

  printf("Always give 100 %% at work!\n");

  for(i = MONDAY; i < NOF_WORKDAYS_IN_WEEK; i++)
  {
    printf(" - %d %% %s\n",workday_efforts[i], workday_names[i]);
    total_effort += workday_efforts[i];
  }
  printf(" %d %% in total !\n", total_effort);
}

顺便说一下,程序的输出是:

始终在工作中给予100%!   - 周一12%   - 周二23%   - 周三40%   - 周四20%   - 周五5%  总计100%!

答案 1 :(得分:1)

问题是在C ++中,编译时常量表达式具有以下限制(5.19常量表达式):

  

一个整数常量表达式只能包含文字(2.13),枚举数,常量变量或用常量表达式(8.5)初始化的整数或枚举类型的静态数据成员,整数或枚举类型的非类型模板参数,以及sizeof表达式。浮动文字(2.13.3)只有在转换为整数或枚举类型时才会出现。只能使用转换为整数或枚举类型的转换。特别是,除了sizeof表达式外,不应使用函数,类对象,指针或引用,也不得使用赋值,递增,递减,函数调用或逗号运算符。

请记住,数组索引表达式实际上只是伪装的指针算术(arr[0]实际上是arr + 0),并且指针不能用于常量表达式,即使它们是指向const的指针数据。所以我认为你在检查数组内容的编译时断言方面运气不好。

C比C ++更受限制,在编译时可以使用这些表达式。

但鉴于C ++的复杂性,也许有人可以提出一种思维开箱即用的解决方案。

答案 2 :(得分:1)

您可以将断言表示为要使用静态分析器检查的属性,并让分析仪进行检查。这具有您想要做的一些属性:

  • 该属性写在源代码

  • 它不会污染生成的二进制代码。

但是,它与编译时断言不同,因为它需要在程序上运行单独的工具以进行检查。也许这是对你试图做的编译器的一个完整性检查,在这种情况下,这没有用,因为静态分析器不检查编译器的功能,只检查应该做什么。 补充:如果是QA,那么编写可以静态验证的“正式”断言现在风靡一时。下面的方法与您可能听说过的.NET契约非常类似,但它适用于C。

  • 您可能不会考虑静态分析器,但是循环和函数调用会导致它们变得不精确。在发生任何这些事件之前,他们更容易清楚地了解初始化时的情况。

  • 有些分析师将自己宣传为“正确”,也就是说,如果您编写的属性超出其功能范围,则它们不会保持沉默。在这种情况下,他们抱怨他们无法证明这一点。如果发生这种情况,在您确信问题出在分析仪而不是阵列之后,您将会离开现在的位置,寻找另一种方式。

以我熟悉的分析仪为例:

const int t[3] = {1, 2, 3};
int x;

int main(){

  //@ assert t[2] == 3 ;

  /* more code doing stuff */
}

运行分析器:

$ frama-c -val t.i
...
t.i:7: Warning: Assertion got status valid.
Values of globals at initialization 
t[0] ∈ {1; }
 [1] ∈ {2; }
 [2] ∈ {3; }
x ∈ {0; }
...

在分析仪的日志中,您将获得:

  • 它认为全局变量的初始值为
  • 的版本
  • 及其对// @评论中所写断言的解释。在这里,它经过一次断言并发现它有效。

使用此类工具的人员会自动构建脚本以从日志中提取他们感兴趣的信息。 但是,作为一个负面的说明,我必须指出,如果你害怕最终会忘记测试,你还应该担心在修改代码后忘记强制静态分析器传递。

答案 3 :(得分:1)

没有。编译时断言根本不适用于你的情况,因为数组“arr [ARR_SIZE]”在链接阶段之前不会存在。

编辑:但是sizeof()似乎有所不同,所以至少你可以这样做:

typedef enum {VALUE_A, VALUE_B,...,VALUE_GGF} VALUES;
const int arr[] = { VALUE_A, VALUE_B, ... ,VALUE_GGF};
#define MY_ASSERT(expr)  {char uname[(expr)?1:-1];uname[0]=0;}
...
// If initialized count of elements is/are not correct, 
//   the compiler will complain on the below line
MY_ASSERT(sizeof(arr) == sizeof(int) * ARR_SIZE)

我在我的FC8 x86系统上测试了代码,但它确实有效。

编辑:注意到@sbi已经将“int arr []”案件弄清楚了。感谢

答案 4 :(得分:0)

由于我正在使用批处理文件来编译和打包我的应用程序,我认为easiset解决方案是编译另一个将运行我的所有数组并验证内容是否正确的简单程序。 如果测试运行失败,我可以通过批处理文件运行测试程序并停止编译其余程序。

答案 5 :(得分:0)

我无法想象为什么你觉得有必要在编译时验证这个,但是有一个可以使用的奇怪/冗长的黑客攻击:

typedef enum {
    VALUE_A, VALUE_B,...,VALUE_GGF
} VALUES;
struct {
    static const VALUES elem0 = VALUE_A;
    static const VALUES elem1 = VALUE_B;
    static const VALUES elem2 = VALUE_C;
    ...
    static const VALUES elem4920 = VALUE_GGF;
    const int operator[](int offset) {return *(&elem0+offset);}
} arr;
void func() {
    static_assert(arr.elem0 == VALUE_A, "arr has been corrupted!");
    static_assert(arr.elem4920 == VALUE_GFF, "arr has been corrupted!");
}

所有这些都在编译时工作。虽然非常黑暗和糟糕的形式。