简体   繁体   中英

C Compile-Time assert with constant array

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?

In the code below, see the extra assignement to pointers declared with fixed length in lines 6 and 9.

This will give errors on compile time if the 2 arrays are not initialized for all values of the WORKDAYS enum. Gcc says: test.c:6:67: warning: initialization from incompatible pointer type [enabled by default]

Imagine some manager adding SATURDAY to the work week enum. Without the extra checks the program will compile, but it will crash with segmentation violation when run.

The downside of this approach is that it takes up some extra memory (I have not tested if this is optimized away by the compiler). It is also a little hackish and probably some comments are required in the code for the next guy...

Please observe that the arrays that are tested should not declare the array size. Setting the array size will ensure that you have reserved the data, but not ensure that it contains something valid.

#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);
}

By the way, the output of the program is:

Always give 100 % at work!
- 12 % Monday
- 23 % Tuesday
- 40 % Wednesday
- 20 % Thursday
- 5 % Friday
100 % in total !

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.

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.

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

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.

I can't imagine why you'd feel the need to verify this at compile time, but there is one wierd/verbose hack that could be used:

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

All of this works at compile time. Very hackish and bad form though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM