views:

472

answers:

4

I have a very big constant array that is initialized at compile time.

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

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

I want to verify that the array is initialized properly, something like:

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

My problem is that I want to verify this at compile time. I've read about compile-time assert in C in this thread: C Compiler asserts. However, the solution offered there suggests to define an array using a negative value as size for a compilation error:

#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];

and use:

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

The solution offered dosn't work for me as I need to verify my constant array values and C doesn't allow to init an array using constant array values:

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

Is there any way around it? Some other compile-time asserts?

+1  A: 

The problem is that in C++ a compile-time constant expression has the following limitations (5.19 Constant expressions):

An integral constant-expression can involve only literals (2.13), enumerators, const variables or static data members of integral or enumeration types initialized with constant expressions (8.5), non-type template parameters of integral or enumeration types, and sizeof expressions. Floating literals (2.13.3) can appear only if they are cast to integral or enumeration types. Only type conversions to integral or enumeration types can be used. In particular, except in sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement, function-call, or comma operators shall not be used.

Remember that an array indexing expression is really just pointer arithmetic in disguise (arr[0] is really arr + 0), and pointers can't be used in constant expressions, even if they're pointers to const data. So I think you're out of luck with a compile time assertion for checking array contents.

C is even more limited than C++ in where these kinds of expressions can be used at compile time.

But given C++'s complexity, maybe someone can come up with a think-outside-the-box solution.

Michael Burr
Const pointer to const expression are out of the question also?
Eldad
@Eldad - by the standard, yes. Some compiler sight be able to offer it as an extension, I suppose. But generally speaking a pointer that's const will be fixed by the linker, not the compiler. I think that's at least part of the reasoning behind the behavior specified by the standard.
Michael Burr
A: 

You can express your assertion as a property to check with a static analyzer and let the analyzer do the check. This has some of the properties of what you want to do:

  • the property is written in the source code,

  • it doesn't pollute the generated binary code.

However, it is different from a compile-time assertion because it needs a separate tool to be run on the program for checking. And perhaps it's a sanity check on the compiler you were trying to do, in which case this doesn't help because the static analyzer doesn't check what the compiler does, only what it should do. ADDED: if it's for QA, then writing "formal" assertions that can be verified statically is all the rage nowadays. The approach below is very similar to .NET contracts that you may have heard about, but it is for C.

  • You may not think much of static analyzers, but it is loops and function calls that cause them to become imprecise. It's easier for them to get a clear picture of what is going on at initialization time, before any of these have happened.

  • Some analyzers advertise themselves as "correct", that is, they do not remain silent if the property you write is outside of their capabilities. In this case they complain that they can't prove it. If this happens, after you have convinced yourself that the problem is with the analyzer and not with your array, you'll be left where you are now, looking for another way.

Taking the example of the analyzer I am familiar with:

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

int main(){

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

  /* more code doing stuff */
}

Run the analyzer:

$ 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; }
...

In the logs of the analyzer, you get:

  • its version of what it thinks the initial values of globals are,
  • and its interpretation of the assertion you wrote in the //@ comment. Here it goes through the assertion a single time and finds it valid.

People who use this kind of tool build scripts to extract the information they're interested in from the logs automatically. However, as a negative note, I have to point out that if you are afraid a test could eventually be forgotten, you should also worry about the mandatory static analyzer pass being forgotten after code modifications.

Pascal Cuoq
Thanks. I'll look more into it, but I feel it's a bit overkill for my purposes as I only need to verify 2 arrays and the frama-c is a 150MB compressed program.
Eldad
A: 

No. Compile-time assertion doesn't work in your case at all, because the array "arr[ARR_SIZE]" won't exist until the linking phase.

EDIT: but sizeof() seems different so at least you could do as the below:

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)

I had tested the code on my FC8 x86 system and it works.

EDIT: noted that @sbi figured "int arr[]" case out already. thanks

EffoStaff Effo
See my comment to @sbi
Eldad
@Eldad: Read that. Well, on "const int arr[]" version, if use ARR_SIZE to represent expected array size as originally, the real array size will not be equal to ARR_SIZE when "add a new ENUM but forget to insert a new entry in the array", so above compile-time assertion "MY_ASSERT(sizeof(arr) == sizeof(int) * ARR_SIZE)" will work.
EffoStaff Effo
I think that my question wasn't clear enough. I updated it:const int arr[VALUE_GGF+1] = { VALUE_A, VALUE_B, ... ,VALUE_GGF};
Eldad
A: 

As I'm using a batch file to compile and pack my application, I think that the easiset solution would be to compile another simple program that will run through all of my array and verify the content is correct. I can run the test program through the batch file and stop compilation of the rest of the program if the test run fails.

Eldad