2

While looking for a compile-time endian detection macro I found this:

#define IS_LITTLE_ENDIAN (1 == *(unsigned char *)&(const int){1})

According to an answer to C Macro definition to determine big endian or little endian machine?, this can be evaluated at compile-time (at least with GCC) and doesn't assume any memory alignment. Is this really portable (provided C99 is available) and if so what are the caveats of this macro?

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
user2711115
  • 457
  • 3
  • 18
  • 2
    Regarding compile-time evaluation, I'm not sure that's a constant expression. For example, I don't think you could have something like `int x[IS_LITTLE_ENDIAN ? 10 : 20];` at file scope. – Kerrek SB Mar 22 '17 at 10:27
  • 1
    I'm not sure how portable anything related to "endianness" is. There's no requirement at all that there are only two kinds of endianness. A hypothetical architecture could easily have padding in its integral representations, or arrange the bits in some non-sequential way. – Kerrek SB Mar 22 '17 at 10:29
  • @KerrekSB It compiles in clang with `-Wall -std=c11` – JeremyP Mar 22 '17 at 10:29
  • @KerrekSB - C11 has a well defined notion of what is a constant expression. And a conditional expression is one such thing if all operands are themselves constant expressions. – StoryTeller - Unslander Monica Mar 22 '17 at 10:29
  • @StoryTeller: Does that include the cast? (Yes, the conditional expression is of course fine, but I was concerned about the cast in the comparison expression.) Reworded. – Kerrek SB Mar 22 '17 at 10:29
  • @KerrekSB - In this particular case, I think not [ see [here](http://port70.net/~nsz/c/c11/n1570.html#6.6p8) ]. But it's not disallowed in general. – StoryTeller - Unslander Monica Mar 22 '17 at 10:31
  • 1
    @KerrekSB My mistake, it does **not** compile at file scope. I had my declaration in a function scope (oops). – JeremyP Mar 22 '17 at 10:33
  • So is the cast to char* the reason its not a constant expression? – user2711115 Mar 22 '17 at 10:37
  • @user2711115 - You got it. – StoryTeller - Unslander Monica Mar 22 '17 at 10:39
  • @StoryTeller thanks, could the use of a union instead of a cast be helpful here? edit: something like this #define IS_BIG_ENDIAN (!(union { uint16_t u16; unsigned char c; }){ .u16 = 1 }.c) from http://stackoverflow.com/a/2100549/2711115 – user2711115 Mar 22 '17 at 10:42
  • 1
    @user2711115 - Not really. The union is not considered a constant expression either (its address is, if you create a compound literal at file scope), but I don't know how that can be useful to you. – StoryTeller - Unslander Monica Mar 22 '17 at 10:45
  • 1
    @user2711115 I do not think there is any way you can do this and have the resulting expression be a compile time constant. The fundamental problem is, I think, that the compiler does not necessarily know the endianness of the target platform. – JeremyP Mar 22 '17 at 10:47
  • @StoryTeller The cast to char* isn't the reason, it's the compound literal that is not a constant. – 2501 Mar 22 '17 at 10:49
  • @2501 - At file scope, the compound literal is a static object, and taking its address produces a constant expression. – StoryTeller - Unslander Monica Mar 22 '17 at 10:51
  • @StoryTeller That is not relevant to what I'm talking about. You agreed that the cast to char* is the reason the expression isn't a constant. That is not correct. I want to point that out so OP won't be mislead. – 2501 Mar 22 '17 at 10:53
  • @2501 - The cast to char* invalidates the expression from being a CE either way. At block scope, it's the literals address that isn't a CE, but what difference does it make any way? The OP can't use that macro as CE endianess indicator. There is nothing misleading about telling them that. – StoryTeller - Unslander Monica Mar 22 '17 at 10:54

1 Answers1

4

There is indeed no undefined behaviour here so it's portable in that sense.

But the condition doesn't necessarily prove IS_LITTLE_ENDIANness.

The storage arrangements of an int are largely left to the implementation. There are other choices other than the classical little and big endian schemes, and your macro could yield a false positive.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 1
    It's not a portable constant expression, which is what I believe the OP was asking. – StoryTeller - Unslander Monica Mar 22 '17 at 10:36
  • 1
    You might be right on a second reading. Let's see how this answer fares in the voting process. – Bathsheba Mar 22 '17 at 10:37
  • Well, the point about it not really testing endianess well, kind of renders the constant expression discussion moot. So you know my opinion vote-wise :) – StoryTeller - Unslander Monica Mar 22 '17 at 10:38
  • 1
    "The storage arrangements of an int are left purely to the implementation." Not entirely sure if this is correct. C allows three different kinds of signedness format. The mostly fictional _one's complement_ and _sign & magnitude_ may have padding bits. I don't see how this code would fail even on such fictional systems though. I suppose it might be better to use `unsigned int`, but it isn't a grave sin to assume that your code will only be executed on computers that actually exist in the real world. – Lundin Mar 22 '17 at 10:57
  • "The mostly fictional one's complement and sign & magnitude may have padding bits" I had that in mind when I answered, without the "mostly fictional" bit. The problem I have with these sort of macros is folk assume they are definitive. But I have dropped the word "purely", replacing with "largely". – Bathsheba Mar 22 '17 at 11:06
  • "There is indeed no undefined behaviour here so it's portable" --> Hmm, I tried compiling `#define IS_LITTLE_ENDIAN (1 == *(unsigned char *)&(const int){1}) #if IS_LITTLE_ENDIAN int test; #endif` with "error: operator '==' has no right operand". So unclear how this is portable, if it does not compile. – chux - Reinstate Monica Mar 22 '17 at 15:13