0

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

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

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

4

6 回答 6

3

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

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

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

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

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

#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%!

于 2012-08-13T15:57:39.647 回答
1

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

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

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

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

但是考虑到 C++ 的复杂性,也许有人可以想出一个跳出框框的解决方案。

于 2009-11-08T08:56:28.880 回答
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; }
...

在分析器的日志中,您会得到:

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

使用这种工具构建脚本的人会自动从日志中提取他们感兴趣的信息。但是,作为负面说明,我必须指出,如果您担心最终会忘记测试,您还应该担心在代码修改后强制静态分析器通过被遗忘。

于 2009-11-08T10:07:20.793 回答
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 []”的情况。谢谢

于 2009-11-09T12:56:13.087 回答
0

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

于 2009-11-10T13:49:04.543 回答
0

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

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!");
}

所有这些都在编译时起作用。虽然非常hackish和糟糕的形式。

于 2011-08-12T20:48:05.130 回答