Conditional expressions involving real (non-integer) constants

Go To Last Post
20 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Greetings,

 

I use a macro to provide a static assert mechanism similar to that provided by C11:

 

#define STATIC_ASSERT(condition, message)               \
  typedef char                                          \
  CAT(                                                  \
      CAT(                                              \
          CAT(                                          \
              CAT(                                      \
                  CAT(STATIC_ASSERT_FAILED___,          \
                      message),                         \
                  ___LINE_),                            \
              __LINE__),                                \
          ___UID_),                                     \
      __COUNTER__)                                      \
    [2*(!!(condition))-1] __attribute__ ((__unused__))

Where:

#define CONCATENATE(a, b) CONCATENATE_(a, b)
#define CONCATENATE_(a, b) a ## b
#define CAT CONCATENATE

This works well, and can be used both at file scope and function scope.

 

I ran into an issue when the condition involves non-integer constants:

 

#define T0 -23.6
#define T1 2.1

STATIC_ASSERT((T1 > T0), T1_must_be_larger_than_T0);

 

This generates a warning (error with -Werror):

error: variably modified 'STATIC_ASSERT_FAILED___T1_must_be_larger_than_T0___LINE_217___UID_0' at file scope [-Werror]
 STATIC_ASSERT(1.0, T1_must_be_larger_than_T0);
 ^

 

The issue seems related to the fact that the arguments in the conditional expression used to compute the size of the array are floating point numbers.  (Note that the fact the arguments are floats does not result in a float for the array size, since the result of the conditional is itself an integer).

 

So the question I have is:  'Why'?

 

They are literals.  They can be evaluated at compile time.  The conditional can be evaluated at compile time.  Why is the compiler concluding that the array size is 'variably modified'?

 

Is this related to a compiler's inability to reduce something like this:

foo = (bar * 2.0) / 5.0;

... into this:

foo = bar * 0.4;

... due to the possibility (though absent in that specific example) of rounding errors?

 

More importantly, is there an alternative?  Other than scaling T0 and T1 into integers?

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Sat. Dec 12, 2015 - 12:24 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think that you are using C89.

IIRC in C89, an array size had to be an integer constant expression.

Again IIRC integer constant expression did not allow using the value of a floating point expression,

not even to compare it to another.

 

I think using C99 or later would cure your problem.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joeymorin wrote:
Is this related to a compiler's inability to reduce something like this:

foo = (bar * 2.0) / 5.0;

... into this:

foo = bar * 0.4;

... due to the possibility (though absent in that specific example) of rounding errors?

Actually, the two expressions are not quite the same.

In floating point, multiplication by the rounded reciprocal is not necessarily the same as division even when both expressions produce finite results.

bar/2.5 is not the same either.

The first might produce an infinity even though the second or mine does not.

Converting one to the other is not allowed.

Some compilers will do it anyway.

gcc will do it if you ask.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

Last Edited: Sat. Dec 12, 2015 - 02:27 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
I think that you are using C89.
Nope.  C99.  Actually, gnu99, but same results.

 

skeeve wrote:
IIRC in C89, an array size had to be an integer constant expression.
That's the thing.  It >>is<< an integer constant expression.  From the .i:

typedef char STATIC_ASSERT_FAILED___T1_must_be_larger_than_T0___LINE_218___UID_0 [2*(!!((2.1 > -23.6)))-1] __attribute__ ((__unused__));

So the expression is:

2*(!!((2.1 > -23.6)))-1

While there are floats in there, they are the arguments to the conditional operator '>'.  The result of that sub-expression is an integer.  Specifically, 0 if false, 1 if true.  The rest of the expression is all integer.

 

skeeve wrote:
Again IIRC integer constant expression did not allow using the value of a floating point expression,

not even to compare it to another.

Right, but the sub-expression (conditional with two float arguments) isn't used directly to size the array.  The integer result of that sub-expression becomes an argument to the remaining all-integer sub-expressions.  Does this still violate the rule?

 

Quote:
I think using C99 or later would cure your problem.
C99, no.  C11 perhaps, but then there would be no point to this macro, since it provides _Static_assert.

 

skeeve wrote:
Actually, the two expressions are not quite the same.
I understand that, and I understand why.  My question was whether the underlying reason for the two issues was the same, i.e. the compiler cannot simplify expressions containing floating point literals, it is obliged to evaluate them at run-time.

 

Quote:
gcc will do it if you ask.
You mean -funsafe-math-optimisations?  I've tried that (and the individual options it implies).  I also tried the broader -ffast-math.  No dice.

 

Or did you mean something else?

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Sat. Dec 12, 2015 - 02:42 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Does C99 permit non-integers in preprocessor comparisons?

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:

Does C99 permit non-integers in preprocessor comparisons?

Not when I just tried it. cheeky

 

However the experiment caused me to think about why - and eventually to the result that joeymorin (OP) isn't ever going to fix this one.

 

Floating point operations are handled by an external library in most C compilers or by hardware floating point units. The results therefore are indeterminate because they depend on the library or whatever hardware processor the code runs on.

 

so in a condensed example of the original code:

 

char bar [(2.1 > -23.6)];

 

gcc must "second-guess" the result of the comparison (even if it's stupidly obvious that it is true) and therefore issues the warning:

warning: variably modified ‘bar’ at file scope

Integer expressions can of course be determined with no ambiguity.

 

 

Last Edited: Sat. Dec 12, 2015 - 02:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think we are looking at compiler bugs.

OP cannot compile with avr-gcc -std=gnu99 even though I expect it should.

Edit: On re-reading n1124, I believe OP's code was invalid even though avr-gcc could sensibly have compiled it. 

With OP's code for op.c, this runs without error message on my PC, even though I expect it shouldn't:

gcc -std=c89 -c -Wall -save-temps -pedantic op.c

op.i:

# 1 "op.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "op.c"
# 22 "op.c"
typedef char STATIC_ASSERT_FAILED___T1_must_be_larger_than_T0___LINE_22___UID_0 [2*(!!((2.1 > -23.6)))-1] __attribute__ ((__unused__));

That brings me to another reason to prefer static in-line function to macros when the choice is available:

A macro expands to a single wonderful-for-reading-and-debugging line.

 

A workaround for OP might be to use gcc extension __builtin_choose_expr (CONST_EXP, EXP1, EXP2)

It works like CONST_EXP ? EXP1 : EXP2 except that it works at compile-time and the type depends on the selection.

EXP1 and EXP2 could be 1 and (void)-3 .

 

Does typdef char fred[2.1> -23.0]; work.

 

 

 

BTW  !! is redundant when applied to a comparison.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

Last Edited: Sat. Dec 12, 2015 - 04:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

N.Winterbottom wrote:
However the experiment caused me to think about why

Because the standard (C90, at least) defines it to be so.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It would seem to be still the same in C99:

 

http://www.open-std.org/jtc1/sc2...

 

See section 6.6.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My comments regarding the non-equivalence of floating point expression were largely unrelated to OP's problem, hence a separate post.

They might relate to the rationale for the relevant rules.

 

In any case, an integer constant expression is not allowed to include a comparison with a floating point number.

A cast from floating constant to integer is allowed.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
A cast from floating constant to integer is allowed.

Is it?

 

I thought it said casts were not allowed ...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Funny, the original reason I wrote that macro (well, adapted someone else's) was to overcome the preprocessor's inability to work with fp expressions.  At the time it worked fine and without any errors because I was using it at function scope.  I only recently tried it with fp literals at file scope and got the warning.

 

What confuses me is that something like this:

int main(void) {
  volatile int bar = (2.1 > -23.6);
  while (1);
}

Results in (even with -O0):

00000000 <main>:
int main(void) {
   0:   cf 93           push    r28
   2:   df 93           push    r29
   4:   00 d0           rcall   .+0             ; 0x6 <__zero_reg__+0x5>
   6:   cd b7           in      r28, 0x3d       ; 61
   8:   de b7           in      r29, 0x3e       ; 62
  volatile int bar = (2.1 > -23.6);
   a:   81 e0           ldi     r24, 0x01       ; 1
   c:   90 e0           ldi     r25, 0x00       ; 0
   e:   9a 83           std     Y+2, r25        ; 0x02
  10:   89 83           std     Y+1, r24        ; 0x01
  while (1);
  12:   ff cf           rjmp    .-2             ; 0x12 <__zero_reg__+0x11>

 

So the compiler is able to reduce the expression (2.1 > -23.6), which is composed strictly of literals, at compile time down to the expected value of 1.  In other words, the fp expression is evaluated in the translation environment.  That can only be true if all operands are constants.

 

skeeve wrote:
With OP's code for op.c, this runs without error message on my PC, even though I expect it shouldn't:
Indeed it does.  That is curious.

 

skeeve wrote:
That brings me to another reason to prefer static in-line function to macros when the choice is available
That would preclude it's use in function scope.  One of the 'goals' of this mechanism is to be able to use the same mechanism at both file and function scope.

 

skeeve wrote:
A workaround for OP might be to use gcc extension
Possibly, but another 'goal' is to avoid the use of tool-chain specific extensions.

 

EDIT:

Apparently not:

typedef char fred[__builtin_choose_expr((2.1> -23.0),1,0)];
error: first argument to '__builtin_choose_expr' not a constant
 typedef char fred[__builtin_choose_expr((2.1> -23.0),1,0)];
                   ^

So (2.1 > -23.0) is apparently not considered to be a constant.  I can't explain that.

 

Again, 4.3.4 has no problem with the above.

 

/EDIT

 

skeeve wrote:
Does typdef(sic) char fred[2.1> -23.0]; work.
With gcc, yes.  On avr-gcc, no.  That made me wonder about compiler versions.  The gcc on this machine is 4.3.4, while avr-gcc is from the Atmel toolchain (3.4.5, so gcc 4.8.1).  I tried avr-gcc 4.3.4 from the machine's distro, and it works fine just like gcc 4.3.4.  So it seems that it is either new standard-compliant behaviour in 4.8.1 (or earlier), or it is a regression.

 

skeeve wrote:
BTW  !! is redundant when applied to a comparison.
When condition is already a conditional expression or a boolean expression, yes.  However it is there to transform any non-zero expression to '1'.  Although pedantic at best, the use of '!!' costs nothing.

 

skeeve wrote:
In any case, an integer constant expression is not allowed to include a comparison with a floating point number.
I don't mean to be obtuse, but I don't understand why, nor can I locate any text in the standard which prohibits this, including in section 6.6 as referenced by Andy above (both in his linked PDF, and in n1256).  Can you quote the specific section in the standard which spells out this prohibition?

 

Oh wait:

6.6.6 An integer constant expression99) shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to the sizeof operator.

 

99) An integer constant expression is used to specify the size of a bit-field member of a structure, the value of an enumeration constant, the size of an array, or the value of a case constant. Further constraints that apply to the integer constant expressions used in conditional-inclusion preprocessing directives are discussed in 6.10.1.

Well, that's damned inconvenient! ;-)

 

And 6.6.7 explains why int bar = (2.1 > -23.6); works without complaint:

6.6.7 More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:

an arithmetic constant expression,

— a null pointer constant,

— an address constant, or

— an address constant for an object type plus or minus an integer constant expression.

 

I am still troubled by the application of 6.6.6 against conditional expressions.  I'm troubled because, even though a conditional expression may have non-integer operands, the result of any conditional expression is always an integer.  And just in case I'm wrong about that and a conditional expression with fp arguments results in fp, note that:

typedef char fred[(int)(2.1> -23.0)];

... also fails.

 

So while the >>arguments<< of a conditional sub-expression may fail to satisfy 6.6.6/99), it does not seem correct for that failure to propagate all the way up the chain of nested sub-expressions.  It seems too literal (pun intended) an interpretation of the standard.

 

EDIT2:  Same warnings issued with gcc 4.7.2 (i.e. on the host gcc, didn't try avr-gcc)  /EDIT2

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Sat. Dec 12, 2015 - 07:05 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joeymorin wrote:
the fp expression is evaluated in the translation environment

Yes: the compiler can do it, but the preprocessor can't.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes: the compiler can do it, but the preprocessor can't.

That's why I said >>translation environment<<.  There has been no question of this being done by the preprocessor.  In fact:

the original reason I wrote that macro (well, adapted someone else's) was to overcome the preprocessor's inability to work with fp expressions.

 

I can't help but conclude that this warning is incorrect behaviour, and an overly zealous interpretation of the standard.

 

I'd be interested to see what other compilers do in the same situation, like CV or ICC, but I don't have those.

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joeymorin wrote:
the result of any conditional expression is always an integer.

Indeed - but just what integer ? It could vary with implementation which is what the compiler tells you.

 

In my earlier post I said your example was blatantly obvious but what about these lines:

 

typedef char foo[(0.3 > 0.2999999999)];
typedef char bar[(0.3 == 0.2999999999)];

 

Depending on the implementation you may get a true or false result for these comparisons.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Depending on the implementation you may get a true or false result for these comparisons.

Yes, but this can be determined in the translation environment i.e. at compile time.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

N.Winterbottom wrote:
joeymorin wrote:
the result of any conditional expression is always an integer.

Indeed - but just what integer ? It could vary with implementation which is what the compiler tells you.

A zero or a one regardless of implementation.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see no reason a compiler could not, as an extension, compile OP's code.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

New plan:

    #define STATIC_ASSERT(condition, message)                                 \
      typedef struct {                                                        \
        int CAT(                                                              \
                CAT(                                                          \
                    CAT(STATIC_ASSERT_FAILED___,                              \
                        message),                                             \
                    ___LINE_),                                                \
                __LINE__) : 2*(!!(condition))-1;                              \
      } CAT(STATIC_ASSERT_UID_, __COUNTER__)  __attribute__ ((__unused__))

 

All versions of gcc and avr-gcc I have tried seem happy with this.

 

However:

6.6.6 An integer constant expression99) shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to the sizeof operator.

 

99) An integer constant expression is used to specify the size of a bit-field member of a structure, the value of an enumeration constant, the size of an array, or the value of a case constant. Further constraints that apply to the integer constant expressions used in conditional-inclusion preprocessing directives are discussed in 6.10.1.

So the same rationale which resulted in recent versions of the compiler (some time after 4.3.4) now issuing a warning for perceived violations of 6.6.6/99) w.r.t array size, should have resulted in the same warning for bit-field member size.  So this is either a non-uniform interpretation of the standard by different GCC contributors, or it is a genuine regression.  GCC source code spelunking would be required to find out which.

 

For now I am satisfied, at least until a future release breaks my new macro ;-)

 

EDIT:  Well, that was premature.  When building with -Wpedantic (as I generally do), I still get a warning:

foo.c:3:16: warning: bit-field 'STATIC_ASSERT_FAILED___message___LINE_3' width not an integer constant expression [-Wpedantic]

/EDIT

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Sun. Dec 13, 2015 - 02:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think that the best you can do is a documented extension.

On avrfreaks, I've seen something like this:

#define staticassume(condition, msg) \
    if(!(condition)) { asm volatile (" .error " msg ); }

After you put a wrapper on it, it can still only be used inside a function,

but that is not much of a limitation.

You would need at most one extra function per file.

They could be garbage collected or placed in the discard section.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles