Detecting Endianness

僤鯓⒐⒋嵵緔 提交于 2019-12-28 02:53:31

问题


I'm currently trying to create a C source code which properly handles I/O whatever the endianness of the target system.

I've selected "little endian" as my I/O convention, which means that, for big endian CPU, I need to convert data while writing or reading.

Conversion is not the issue. The problem I face is to detect endianness, preferably at compile time (since CPU do not change endianness in the middle of execution...).

Up to now, I've been using this :

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
...
#else
...
#endif

It's documented as a GCC pre-defined macro, and Visual seems to understand it too.

However, I've received report that the check fails for some big_endian systems (PowerPC).

So, I'm looking for a foolproof solution, which ensures that endianess is correctly detected, whatever the compiler and the target system. well, most of them at least...

[Edit] : Most of the solutions proposed rely on "run-time tests". These tests may sometimes be properly evaluated by compilers during compilation, and therefore cost no real runtime performance.

However, branching with some kind of << if (0) { ... } else { ... } >> is not enough. In the current code implementation, variable and functions declaration depend on big_endian detection. These cannot be changed with an if statement.

Well, obviously, there is fall back plan, which is to rewrite the code...

I would prefer to avoid that, but, well, it looks like a diminishing hope...

[Edit 2] : I have tested "run-time tests", by deeply modifying the code. Although they do their job correctly, these tests also impact performance.

I was expecting that, since the tests have predictable output, the compiler could eliminate bad branches. But unfortunately, it doesn't work all the time. MSVC is good compiler, and is successful in eliminating bad branches, but GCC has mixed results, depending on versions, kind of tests, and with greater impact on 64 bits than on 32 bits.

It's strange. And it also means that the run-time tests cannot be ensured to be dealt with by the compiler.

Edit 3 : These days, I'm using a compile-time constant union, expecting the compiler to solve it to a clear yes/no signal. And it works pretty well : https://godbolt.org/g/DAafKo


回答1:


At compile time in C you can't do much more than trusting preprocessor #defines, and there are no standard solutions because the C standard isn't concerned with endianness.

Still, you could add an assertion that is done at runtime at the start of the program to make sure that the assumption done when compiling was true:

inline int IsBigEndian()
{
    int i=1;
    return ! *((char *)&i);
}

/* ... */

#ifdef COMPILED_FOR_BIG_ENDIAN
assert(IsBigEndian());
#elif COMPILED_FOR_LITTLE_ENDIAN
assert(!IsBigEndian());
#else
#error "No endianness macro defined"
#endif

(where COMPILED_FOR_BIG_ENDIAN and COMPILED_FOR_LITTLE_ENDIAN are macros #defined previously according to your preprocessor endianness checks)




回答2:


As stated earlier, the only "real" way to detect Big Endian is to use runtime tests.

However, sometimes, a macro might be preferred.

Unfortunately, I've not found a single "test" to detect this situation, rather a collection of them.

For example, GCC recommends : __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ . However, this only works with latest versions, and earlier versions (and other compilers) will give this test a false value "true", since NULL == NULL. So you need the more complete version : defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)

OK, now this works for newest GCC, but what about other compilers ?

You may try __BIG_ENDIAN__ or __BIG_ENDIAN or _BIG_ENDIAN which are often defined on big endian compilers.

This will improve detection. But if you specifically target PowerPC platforms, you can add a few more tests to improve even more detection. Try _ARCH_PPC or __PPC__ or __PPC or PPC or __powerpc__ or __powerpc or even powerpc. Bind all these defines together, and you have a pretty fair chance to detect big endian systems, and powerpc in particular, whatever the compiler and its version.

So, to summarize, there is no such thing as a "standard pre-defined macros" which guarantees to detect big-endian CPU on all platforms and compilers, but there are many such pre-defined macros which, collectively, give a high probability of correctly detecting big endian under most circumstances.




回答3:


Instead of looking for a compile-time check, why not just use big-endian order (which is considered the "network order" by many) and use the htons/htonl/ntohs/ntohl functions provided by most UNIX-systems and Windows. They're already defined to do the job you're trying to do. Why reinvent the wheel?




回答4:


Try something like:

if(*(char *)(int[]){1}) {
    /* little endian code */
} else {
    /* big endian code */
}

and see if your compiler resolves it at compile-time. If not, you might have better luck doing the same with a union. Actually I like defining macros using unions that evaluate to 0,1 or 1,0 (respectively) so that I can just do things like accessing buf[HI] and buf[LO].




回答5:


Notwithstanding compiler-defined macros, I don't think there's a compile-time way to detect this, since determining the endianness of an architecture involves analyzing the manner in which it stores data in memory.

Here's a function which does just that:

bool IsLittleEndian () {

    int i=1;

    return (int)*((unsigned char *)&i)==1;

}



回答6:


As others have pointed out, there isn't a portable way to check for endianness at compile-time. However, one option would be to use the autoconf tool as part of your build script to detect whether the system is big-endian or little-endian, then to use the AC_C_BIGENDIAN macro, which holds this information. In a sense, this builds a program that detects at runtime whether the system is big-endian or little-endian, then has that program output information that can then be used statically by the main source code.

Hope this helps!




回答7:


This comes from p. 45 of Pointers in C:

#include <stdio.h>
#define BIG_ENDIAN 0
#define LITTLE_ENDIAN 1

int endian()
{
   short int word = 0x0001;
   char *byte = (char *) &word;
   return (byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}

int main(int argc, char* argv[])
{
   int value;
   value = endian();
   if (value == 1)
      printf("The machine is Little Endian\n");
   else
      printf("The machine is Big Endian\n");
   return 0;
}



回答8:


You can't detect it at compile time to be portable across all compilers. Maybe you can change the code to do it at run-time - this is achievable.




回答9:


It is not possible to detect endianness portably in C with preprocessor directives.




回答10:


Socket's ntohl function can be used for this purpose. Source

// Soner
#include <stdio.h>
#include <arpa/inet.h>


int main() {
    if (ntohl(0x12345678) == 0x12345678) {
        printf("big-endian\n");
    } else if (ntohl(0x12345678) == 0x78563412) {
        printf("little-endian\n");
    } else {
        printf("(stupid)-middle-endian\n");
    }
    return 0;
}



回答11:


I took the liberty of reformatting the quoted text

As of 2017-07-18, I use union { unsigned u; unsigned char c[4]; }

If sizeof (unsigned) != 4 your test may fail.

It may be better to use

union { unsigned u; unsigned char c[sizeof (unsigned)]; }



回答12:


As most have mentioned, compile time is your best bet. Assuming you do not do cross compilations and you use cmake (it will also work with other tools such as a configure script, of course) then you can use a pre-test which is a compiled .c or .cpp file and that gives you the actual verified endianness of the processor you're running on.

With cmake you use the TestBigEndian macro. It sets a variable which you can then pass to your software. Something like this (untested):

TestBigEndian(IS_BIG_ENDIAN)
...
set(CFLAGS ${CFLAGS} -DIS_BIG_ENDIAN=${IS_BIG_ENDIAN}) // C
set(CXXFLAGS ${CXXFLAGS} -DIS_BIG_ENDIAN=${IS_BIG_ENDIAN}) // C++

Then in your C/C++ code you can check that IS_BIG_ENDIAN define:

#if IS_BIG_ENDIAN
    ...do big endian stuff here...
#else
    ...do little endian stuff here...
#endif

So the main problem with such a test is cross compiling since you may be on a completely different CPU with a different endianness... but at least it gives you the endianness at time of compiling the rest of your code and will work for most projects.




回答13:


I know I'm late to this party, but here is my take.

int is_big_endian() {
    return 1 & *(uint16_t*)"01";
}

This is based on the fact that '0' is 48 in decimal and '1' 49, so '1' has the LSB bit set, while '0' not. I could make them '\x00' and '\x01' but I think my version makes it more readable.




回答14:


#define BIG_ENDIAN ((1 >> 1 == 0) ? 0 : 1)


来源:https://stackoverflow.com/questions/8978935/detecting-endianness

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!