A C++-Language Question (Maybe C, too)

Technically this should be on the C++ language group, but I want an answer, not to either be sneered at or to start a religious war.

I think this works the same if it's C, once you take out the C++-isms.

So, here's an array definition. All is hunky-dory -- I'm specifying four English-language strings for four possible fault conditions. No prob.

const char * const CHealth::_faultStrings[4] = { "no status message", "user fault test", "motor supply over limit", "motor supply under limit" };

Under the gnu compiler, if I leave out one of the commas this construct compiles without a whimper -- it just makes three strings, one of which is longer than you thought. Then later, when you get a motor supply under limit fault, it happily takes an uninitialized pointer and crashes the machine (or not, depending on build details).

Is there a way to write this so that if you specify a too-short array the compiler will automatically complain? Causing an error would be best, but popping a warning would be OK since I've got the thing flagged to error out on warnings.

Knowing some g++ flag to add would work for me for now; having some guaranteed way to make any ANSI-conformant compiler bomb during the build would be better.

--
My liberal friends think I'm a conservative kook. 
My conservative friends think I'm a liberal kook. 
 Click to see the full signature
Reply to
Tim Wescott
Loading thread data ...

If you define that array as static, then I'm pretty sure that by the C spec any terms not explicitly initialized should be set to 0. Then you can iterate over the array and check for null pointers.

Not a clean answer, but maybe an answer.

--
Rob Gaddi, Highland Technology -- www.highlandtechnology.com 
Email address domain is currently out of order.  See above to fix.
Reply to
Rob Gaddi

An imperfect solution: Create _faultStrings[] as an array of unspecified size. #include and then insert a line

assert(sizeof(_faultStrings) / sizeof(_faultStrings[0]) == 4);

after this and similar arrays are defined.

This will cause a *runtime* error and not a compile time error, which is why it's imperfect. Also, if NDEBUG is defined when assert.h is included, the assert() macros are "compiled out" of the executable. For an embedded app, you can typically write your own __assert() function that yells on a serial port or sounds a klaxton or ...

Reply to
Rich Webb

I'd use the cardinality of the _faultStrings array ( and get rid of the [4], replaced with a [] ) and an assert.

To wit: ...

// sizeof a pointer is still 4 or 8.... const int CHealth::_NumFaultStrings = ( ( sizeof(CHealth::_faultStrings) ) / (sizeof(CHealth::_faultStrings[0]) ) ) ;

...

// hopefully, we're someplace where an assert is appropriate, like in // main() or the initializer routine for CHealth assert(CHealth::NumFaultStrings == 4);

There may be some way to rig up #if and #error to improve on that, but I always have to experiment with those. They're kinda ugly, too.

There may be other ways to make life a tad more miserable while forcing each delimited string to be a separate thing - like a struct declaration which would drive you into using curly braces.

Some things are just why we do lots of unit testing, ultimately. Unit testing can easily miss similar things, but it's another arrow in the quiver.

--
Les Cargill
Reply to
Les Cargill

You can use: const char * const CHealth::_faultStrings[4] = { [0] = "no status message", [1] = "user fault test", [2] = "motor supply over limit", [3] = "motor supply under limit" }; You will get an error if you miss a comma.

Or something like #define NO_STATUS_MESSAGE 0

[NO_STATUS_MESSAGE] = "no status message",

Strict order is no longer important. Works great when you use faultStrings[] =

and a simple check to see if your message index is out of bounds

--
Chisolm 
Republic of Texas
Reply to
Joe Chisolm

You can convert the runtime error into compile time by using a "static_assert", typically done by creating an array time is a size of 0 elements for the case of an error, which is a constraint violation.

Reply to
Richard Damon

I like the idea of finding a compiler option that would detect and report an indufficiently initialized array. But as you say, it would be compiler-dependent. What C does (and I'm not certain about C++, because the concept of "semantic zero" may have been modified given its support for objects) is to set all additional entries in your array, beyond those explicitly initialized, into a semantic zero. In the case of your array, this means NULL pointers. You could easily check that at run time, of course. But you want a compile-time error.

Another possibility is an idiom I've seen a few places and which should work on C and C++ (though C++ disparages use of #define.)

#define MYCAT_(a, b) a ## b #define MYCAT(a, b) MYCAT_(a, b) #define MYASSERT(cond) typedef int \ MYCAT(AsSeRt, __LINE__)[(cond) ? 1 : -1]

I apologize for switching your array name around but it was long, so I shortened it to 'f'. So your array is:

const char * const f[]= { "no status message", "user fault test", "motor supply over limit" "motor supply under limit" };

Note two changes. I didn't specify the size of the array at the outset, namely the [4] is now [] instead. Then I also removed one of the commas as you mentioned. So now C and C++ will concatenate (as they are designed to do) the last two strings. This means that only 3 entries will be created and so it will be the case that f[] will only be sized for 3.

Now this works:

MYASSERT( (sizeof(f)/sizeof(f[0]) == 4) );

And should given an error given the above array creation.

It's too bad that #if can't use sizeof(), but it can't. Otherwise you could just use that and be done with it. But at least the above will work on C and C++ compilers, I think.

Jon

Reply to
Jon Kirwan

? or by using static_assert itself, which is part of ISO C++11 and allows having nice sensible messages output when the assertion fails.

Chris

Reply to
Christopher Head

Which assumes you have ISO C++11. I work on embedded devices which are, mostly, era C89/90 compiler tools with perhaps a few bits and pieces from C99 when I'm lucky. Heck. In some cases, I'm actually using a mid 1980's C compiler, in fact, to support some old instrument that is still being sold and modified even today. (Like Lattice C.)

Jon

Reply to
Jon Kirwan

static_assert is new in C++11. While C++11 support is progressing rapidly, embedded systems are often a few versions behind the mainstream desktop/server platforms.

Reply to
Nobody

Uh... I meant insufficiently. ;)

Reply to
Jon Kirwan

The trouble is, skipping a comma leaves you with perfectly good C code - the strings are concatenated, and missing initialisers are left as 0. gcc does not have any warnings that would catch the mistake (AFAIK).

Two ideas spring to mind. First, remember that it is also legal C to include an extra comma at the end of the last message - then at least all your lines are consistent, and it is much easier if you need to re-arrange them, copy-and-paste, etc.

Secondly, I'd drop the "4" from the initial definition, then use a static assertion to check the size of the array. If you are using the latest C or C++ standards, you get proper static assertions - if not, you can hack one together using macros.

Reply to
David Brown

Yes, static_assert and _Static_assert are both missing in all of my embedded environments.

Just checked in mingw's gcc (4.6.2 in the version I'm using) and it handles _Static_assert properly but the static_assert macro is undefined (with assert.h included).

I guess it's coming to the time when I'll actually have to sit down and *read* the new standard...

Reply to
Rich Webb
[...]

A shoot in the air: have you added "-std=c++11" to your CFLAGS?

--
FSF associate member #7257	http://hfday.org/
Reply to
Ivan Shmakov

The latest this particular release goes is "-std=c1x Conform to the ISO 201X C standard draft" and it flags the plain "static_assert()" as an undefined reference.

Interestingly, _Static_assert(sizeof... etc) seems to be supported even with "-std=c89" given as an option, so it's apparently a library function. Who knew? Handy, though.

The standard also seems to read that the expansion of the macro static_assert to _Static_assert is not masked by the presence of NDEBUG.

Reply to
Rich Webb

Simpler and shorter: #define MYASSERT(cond) extern int MYASSERT[(cond) ? 1 : -1]; No need to mess with macro concatenation. An 'extern' declaration can appear multiple times, so you don't have to invent a new name each time.

In C++, you can also replace 'extern' with 'typedef' to get a version that also works within class definitions. C++ allows a typedef with the same name to reappear multiple times.

Another way to make a compiler bomb when initialisations are missing is to wrap it in a structure that ends in an improbable type: typedef struct improbable improbable_t(); struct table { const char* strings[4]; improbable_t* guard; }; static struct table x = { "no status message", "user fault test", "motor supply over limit", "motor supply under limit", (improbable_t*) 0, }; Since this improbable type doesn't auto-convert to anything, you'll get an error when it appears in the wrong place.

Despite its drawbacks (warning for missing braces, wastes a word of memory) I had to use this a few times.

Stefan

Reply to
Stefan Reuther

Now that I'm looking, rather than just copying out an idiom, I believe it had something to do expanding out macros like __LINE__ or something like that. Have to try it to find out, though. It's just a guess, right now.

Jon

Reply to
Jon Kirwan

The problem is you are asking to receive a warning for what is perfectly valid C code. GCC options such as -Wmissing-field-initializers will warn against missing structure initialisers, but not array initialisers.

If you don't want to add explicit diagnostic code into your main program (to manually check the size of the array), I think you could instead add in some kind of automatic static analysis into your build scripts. Running Lint after GCC would probably bring the issue to your attention.

Regards, Richard.

  • formatting link
    Designed for microcontrollers. More than 103000 downloads in 2012.

  • formatting link
    Trace, safety certification, UDP/IP, TCP/IP, training, and more...

Reply to
FreeRTOS info

I like this solution. It's simple, and by using names, you also protect yourself against fault strings being in the wrong order.

When printing the fault string, I would also add a check for 0, and print a generic numbered error message instead.

Reply to
Arlet Ottens

Not only, that, but the explicit index number make it easy to keep track of the number and position of the strings in the list. I also like the defined constants for the indices. That could be very useful when it comes time to document your code for the next person to maintain it.

Mark Borgerson

Reply to
Mark Borgerson

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.