quest for cross-platform static assert

F

Francois Grieu

Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]

e.g. in

ASSERT_S(sizeof(foo)>=sizeof(bar));
memcpy(foo,bar,sizeof(bar));


But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). On these platforms
I end up using

#define ASSERT_S(condition) struct{char assert_s[(condition)?1:-1];}

but it causes other issues on some other platforms. I recall things
depend on if ASSERT_S is used in global or function scope.

There is

#define ASSERT_S(condition) enum{ASSERT_Sy(__LINE__)=sizeof(char[(condition)?1:-1])}
#define ASSERT_Sy(j) ASSERT_Sz(j)
#define ASSERT_Sz(j) assert_s##j

and it often works fine, but using that ASSERT_S twice on same-numbered
lines (e.g twice on a line, or different but same-numbered lines in different
header files used in the same compilation unit) will fail.


Except when __COUNTER__ is available, workarounds get hairy:
#define ASSERT_S(condition) enum{ASSERT_Sy(__LINE__,HDR_NAME)=sizeof(char[(condition)?1:-1])}
#define ASSERT_Sy(j,n) ASSERT_Sz(j,n)
#define ASSERT_Sz(j,n) assert_s##j##n

with the convention that any header file should be structured as
#include "other_header.h"
#define HDR_NAME myuniquename
/* here ASSERT_S is usable */
#undef HDR_NAME


Can this be improved upon, towards the grail of working almost everywhere
without special-casing for this or that environment?

TIA,
Francois Grieu
 
C

Chris H

Francois Grieu said:
Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time.
Snip

Can this be improved upon, towards the grail of working almost everywhere
without special-casing for this or that environment?

It is not a holy grail. Most don't care about portability. Most of those
who do only care for a very small sub set of environments.
 
F

Francois Grieu

Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]

switch statement with two cases, one case 0: and one case
yourcondition: If the condition is false, you have two case labels =
0, and I haven't seen a compiler accepting it, and I haven't seen a
compiler giving warnings about it. For example

switch (0) { case 0: case sizeof (long) == 4: ; }

(in some macro obviously).

Obvious drawbacks:
- not usable in a header file.
- generates code on many platforms.

Francois Grieu
 
F

Francois Grieu

It is not a holy grail. Most don't care about portability. Most of those
who do only care for a very small sub set of environments.

Most pursue other grails than me.

Francois Grieu
 
I

ImpalerCore

Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]

e.g. in

ASSERT_S(sizeof(foo)>=sizeof(bar));
memcpy(foo,bar,sizeof(bar));

But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). On these platforms
I end up using

#define ASSERT_S(condition) struct{char assert_s[(condition)?1:-1];}

but it causes other issues on some other platforms. I recall things
depend on if ASSERT_S is used in global or function scope.

There is

#define ASSERT_S(condition) enum{ASSERT_Sy(__LINE__)=sizeof(char[(condition)?1:-1])}
#define ASSERT_Sy(j) ASSERT_Sz(j)
#define ASSERT_Sz(j) assert_s##j

and it often works fine, but using that ASSERT_S twice on same-numbered
lines (e.g twice on a line, or different but same-numbered lines in different
header files used in the same compilation unit) will fail.

Except when __COUNTER__ is available, workarounds get hairy:
#define ASSERT_S(condition) enum{ASSERT_Sy(__LINE__,HDR_NAME)=sizeof(char[(condition)?1:-1])}
#define ASSERT_Sy(j,n) ASSERT_Sz(j,n)
#define ASSERT_Sz(j,n) assert_s##j##n

with the convention that any header file should be structured as
#include "other_header.h"
#define HDR_NAME myuniquename
/* here ASSERT_S is usable */
#undef HDR_NAME

Can this be improved upon, towards the grail of working almost everywhere
without special-casing for this or that environment?

Not sure if you would consider this an improvement, but my version
parameterizes the name.

\code snippet
#define C_STATIC_ASSERT(name, expr) extern char (name)[(expr) ? 1 :
-1]

C_STATIC_ASSERT( CHAR_BIT_is_8_bits, CHAR_BIT == 8 );
C_STATIC_ASSERT( sizeof_int_at_least_32_bits, sizeof(int) >= 4 );
\endcode

That way, when you get a failure, you get the label in the error
output like this:

C_STATIC_ASSERT_example.c:4: error: size of array
`sizeof_int_at_least_32_bits' is negative.

You could try giving each static assertion a unique name and see if
it's more portable in your environments.

Best regards,
John D.
 
T

Tim Rentsch

Francois Grieu said:
Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]

e.g. in

ASSERT_S(sizeof(foo)>=sizeof(bar));
memcpy(foo,bar,sizeof(bar));


But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). [snip alternatives]

Any reason you can't simply add a definition (perhaps in its
own separate .c file) for the symbol in question? I know it
adds some configuration overhead that you may not want to
add, but it would seem to completely solve the portability
problem.
 
F

Francois Grieu

Francois Grieu said:
Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]

e.g. in

ASSERT_S(sizeof(foo)>=sizeof(bar));
memcpy(foo,bar,sizeof(bar));


But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). [snip alternatives]

Any reason you can't simply add a definition (perhaps in its
own separate .c file) for the symbol in question? I know it
adds some configuration overhead that you may not want to
add, but it would seem to completely solve the portability
problem.

I remember using this workaround, with success, on I-can't-remember-what
platform where the linker complained. Having to bundle a .c file just for
that is a pain (currently my "commonstuff" header has no such requirement).

And I tried again that variant of ASSERT_S on a platform where I
currently special-case to use the dummy-struct technique, and got that
spurious compiler warning:
#warning cpstm7 sha1.c:75(2+15) assert_failure unused

This happens when ASSERT_S is used in function scope. Quite logical.

BTW, I also have the variant below which has been 100% portable so far,
but has the drawback of being usable only in function scope and after
the last variable declaration (by contract, ASSERT_S is usable anywhere
a declaration is possible).

#define ASSERT_F(condition) do;while(!sizeof(char[(condition)?1:-1]))


Francois Grieu
 
C

Chris H

Francois Grieu said:
Most pursue other grails than me.

Francois Grieu

SO why do you want to be able to run on a PIC, PowerPC, Alpha, MIPS,
ARM, 8051 and x86?
 
E

Eric Sosman

Francois Grieu said:
Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]
[...]
But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). [snip alternatives]

Any reason you can't simply add a definition (perhaps in its
own separate .c file) for the symbol in question? [...]

I remember using this workaround,[...]

Peculiar use of "workaround," don't you think? You promise the
compiler that some other compilation unit defines `assert_failure',
and it's a "workaround" to provide the promised definition? And the
complaint when you renege on your promise is "spurious?"

Ever hear the one about the guy who murdered his parents and
asked the court for mercy because he was an orphan?
 
F

Francois Grieu

Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]
[...]
But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). [snip alternatives]

Any reason you can't simply add a definition (perhaps in its
own separate .c file) for the symbol in question? [...]

I remember using this workaround,[...]

Peculiar use of "workaround," don't you think? You promise the
compiler that some other compilation unit defines `assert_failure',
and it's a "workaround" to provide the promised definition? And the
complaint when you renege on your promise is "spurious?"

On the other hand, I never make a reference to that assert_failure[],
therefore it seems at least allowable that the linker does not complain
or/and that the compiler does not even inform the linker about that
assert_failure[] thing. That happens on most of my platforms.
Ever hear the one about the guy who murdered his parents and
asked the court for mercy because he was an orphan?

That guy was adopted.


Francois Grieu
 
F

Francois Grieu

So why do you want to be able to run on a PIC, PowerPC, Alpha, MIPS,
ARM, 8051 and x86?

Some algorithms (e.g. SHA1) make sense on all these platforms.

I have compiled that SHA1 library on 5 out of 7 of these platforms
in the last 12 month, plus 4 others, often with several versions
or several compilers from several vendors, some of which are no
longer in business. That's why cross-platform portability matters
from me. That's actually one of the great strength of C.

Francois Grieu
 
B

Ben Bacarisse

Eric Sosman said:
Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]
[...]
But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). [snip alternatives]

Any reason you can't simply add a definition (perhaps in its
own separate .c file) for the symbol in question? [...]

I remember using this workaround,[...]

Peculiar use of "workaround," don't you think? You promise the
compiler that some other compilation unit defines `assert_failure',

In C99 at least, the external declaration doesn't constitute such a
promise. If the object is used (other than as an operand of sizeof)
then there must be a definition, but when it is not used there need not
be one (though it is not an error to provide one).
and it's a "workaround" to provide the promised definition? And the
complaint when you renege on your promise is "spurious?"

I agree in that I don't think the wording (see 6.9 p5) is strong enough
to suggest that a diagnostic would be "spurious", but I think the
program must be accepted. (Well, it can't be rejected for this reason
alone.)

<snip>
 
K

Keith Thompson

Chris H said:
SO why do you want to be able to run on a PIC, PowerPC, Alpha, MIPS,
ARM, 8051 and x86?

Why not?

If there's some advantage to writing code that will only work
on a subset of implementations, then by all means do so. But if
it's just as easy to write code that will work on any conforming
C implementation, why not do it? It means, among other things,
that the code will continue to work on future C implementations.
 
C

Chris H

Francois Grieu said:
Some algorithms (e.g. SHA1) make sense on all these platforms.

I have compiled that SHA1 library on 5 out of 7 of these platforms
in the last 12 month, plus 4 others, often with several versions
or several compilers from several vendors, some of which are no
longer in business. That's why cross-platform portability matters
from me. That's actually one of the great strength of C.

Francois Grieu


Point taken... however often to male these algorithms operate
efficiently you often have to use architecture/ compiler specific
extensions.

Just out of curiosity which were the two you did not compile on?
 
F

Francois Grieu

Point taken... however often to make these algorithms operate
efficiently you often have to use architecture/compiler specific
extensions.

Just out of curiosity which were the two you did not compile on?

Alpha, MIPS. The later is very much alive in some application fields
(e.g. set-top boxes).

Francois Grieu
 
F

Francois Grieu

Eric Sosman said:
On 20/04/2011 20:04, Tim Rentsch wrote:

Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]
[...]
But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). [snip alternatives]

Any reason you can't simply add a definition (perhaps in its
own separate .c file) for the symbol in question? [...]

I remember using this workaround,[...]

Peculiar use of "workaround," don't you think? You promise the
compiler that some other compilation unit defines `assert_failure',

In C99 at least, the external declaration doesn't constitute such a
promise. If the object is used (other than as an operand of sizeof)
then there must be a definition, but when it is not used there need not
be one (though it is not an error to provide one).
and it's a "workaround" to provide the promised definition? And the
complaint when you renege on your promise is "spurious?"

I agree in that I don't think the wording (see 6.9 p5) is strong enough
to suggest that a diagnostic would be "spurious", but I think the
program must be accepted. (Well, it can't be rejected for this reason
alone.)

Thanks for pointing 6.9 p5. Here it is, with note 136.

An 'external definition' is an external declaration that is also a
definition of a function (other than an inline definition) or an object.
If an identifier declared with external linkage is used in an expression
(other than as part of the operand of a sizeof operator whose result is
an integer constant), somewhere in the entire program there shall be
exactly one external definition for the identifier; otherwise, there shall
be no more than one.
(note 136) Thus, if an identifier declared with external linkage is not
used in an expression, there need be no external definition for it.


Looks like I'm far from contempt of Court doing what I do.

Francois Grieu
 
F

Francois Grieu

I sorta wrote:


/* Cause a *compilation* error when condition is not met.
The test condition must be an "arithmetic constant expression"
Should be ysable anywhere a declaration is allowed */
#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]

#include <time.h>

ASSERT_S(CLOCKS_PER_SEC>=1); /* insure time unit is at most 1 second */

/* setup a CPU time limit, expressed in seconds */
time_t cpu_limiter_init(unsigned long seconds) {
/* check wraparound does not occur before two weeks */
ASSERT_S((time_t)(1300000UL*CLOCKS_PER_SEC)/CLOCKS_PER_SEC>1299000UL);
time_t result = clock() + seconds*CLOCKS_PER_SEC;
return result;
}


and, on one compiler, I get that warning for the use of ASSERT_S
inside the scope of cpu_limiter_init:
#warning [..] assert_failure unused.


On that system this kludge seems to avoid the warning:

#define ASSERT_S(condition) \
extern char assert_failure[(condition)?1:-1];\
extern char assert_failure[sizeof assert_failure]

But is it safe to assume this works on every platform where the
original cause no trouble?


Francois Grieu
 
S

Seebs

If there's some advantage to writing code that will only work
on a subset of implementations, then by all means do so. But if
it's just as easy to write code that will work on any conforming
C implementation, why not do it? It means, among other things,
that the code will continue to work on future C implementations.

By default, at my day job, I am working with code which we expect to run
reliably across maybe 50ish CPUs. See, we're doing embedded systems...
But we're doing *tools* for embedded systems, meaning that while any
given user will probably be entirely happy with extremely bare-metal
code that relies closely on given hardware traits, we don't know in advance
which of those 50ish CPUs they are comfortable relying on.

I have, on more than one occasion now, been asked whether a given hunk of
code was likely to run on a new CPU of an archtecture previously unknown
to me. Thus far, the answer has been "yes".

-s
 
T

Tim Rentsch

Francois Grieu said:
Francois Grieu said:
Under C89/90/99, there is no static assert allowing to check a
constant expression at compile time. I often use

#define ASSERT_S(condition) extern char assert_failure[(condition)?1:-1]

e.g. in

ASSERT_S(sizeof(foo)>=sizeof(bar));
memcpy(foo,bar,sizeof(bar));


But on some platforms there is a spurious warning or link time error
(which I can't remember, and that would be OT). [snip alternatives]

Any reason you can't simply add a definition (perhaps in its
own separate .c file) for the symbol in question? I know it
adds some configuration overhead that you may not want to
add, but it would seem to completely solve the portability
problem.

I remember using this workaround, with success, on I-can't-remember-what
platform where the linker complained. Having to bundle a .c file just for
that is a pain (currently my "commonstuff" header has no such requirement).

Yes, sad but true.
And I tried again that variant of ASSERT_S on a platform where I
currently special-case to use the dummy-struct technique, and got that
spurious compiler warning:
#warning cpstm7 sha1.c:75(2+15) assert_failure unused

This happens when ASSERT_S is used in function scope. Quite logical.

Again sad but true.
BTW, I also have the variant below which has been 100% portable so far,
but has the drawback of being usable only in function scope and after
the last variable declaration (by contract, ASSERT_S is usable anywhere
a declaration is possible). [snip alternative]

If it were me, I think I'd prefer the static assert macro to
be an <expression> rather than a <declaration>, and live
with the annoyance of not being able to use it except in
expression contexts. (Alternatively, have two forms, one
for top-level declarations, one for expressions, with the
former defined in terms of the latter.) Besides being able
to put assertions in more places, the <expression> form is
probably easier to write in a way that is sure to be
consistently reliable. I don't mind having to use two
different forms for the different contexts, if what I get
for it is more confidence in the reliability.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top