Detecting Endianness
Asked Answered
N

18

32

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

Niobous answered 23/1, 2012 at 21:35 Comment(8)
@BoPersson - this is not a compile time detectionPascoe
Run time is your best bet, but compile time is included in the below answers: 1. https://mcmap.net/q/18517/-detecting-endianness-programmatically-in-a-c-program 2. https://mcmap.net/q/18518/-macro-definition-to-determine-big-endian-or-little-endian-machineDelorsedelos
Some CPUs actually can have different endianness for different executables. en.wikipedia.org/wiki/Endianness#Bi-endian_hardwareStork
@bo : indeed, i've checked this question too, but unfortunately it's a run-time detection. It advises to depend on the result of a function. This might be okay for some other use, but I need a compile (preprocessor) detection.Niobous
@Niobous , bar the ones you've mentioned, there isn't one. So either compile a small program that detects the endianess, and feed the result into your build system so it defines a preprocessor macro, or write the code so it is independent of the host endianess.Snag
Run-time detection is a great interview question :)Driftage
The reason your preprocessor-based test can fail (false positive) is that undefined symbols get replaced with 0 in #if directives.Viviparous
@R. : +1, yes, i guess this is why it seems to works for Visual.Niobous
S
19

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)

Skeens answered 23/1, 2012 at 21:43 Comment(9)
The value of a union member other than the last one stored into is an unspecified behavior in C.Growl
@ouah: the C standard knows nothing about endianness, so we are already going out of the standard domain and working on implementation-specific behavior (and I don't think you'll ever find a compiler implementing unions differently or an optimizer messing with them). Although, I agree that the other "classic method" (cast of the pointer to char *) does not exhibit UB problems due to the exceptions to the aliasing rules.Skeens
@ouah: also, §6.7.2.1 doesn't mention UB, it just says that "The value of at most one of the members can be stored in a union object at any time"; also, I dare to say that §6.7.2.1 ¶14 implicitly allows the use of unions as a replacement for that cast, since "A pointer to a union object, suitably converted, points to each of its members [...] and vice versa.". So, &u.i = &u = &u.c (with the appropriate casts), thus u.c[0] = (*(&u.c))[0]=*((char *)&u.i), which is as legal as the "other method".Skeens
In C99, Annex J (non-normative) "J.1 Unspecified behavior. The following are unspecified: The value of a union member other than the last one stored into (6.2.6.1)." and 6.2.6.1p7 says "When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values."Growl
@ouah: the first one is solved by working on §6.7.2.1 ¶14 as I already wrote before (it's still unspecified behavior, but exactly as it is the cast - and hey, that code is there to understand exactly how the compiler implements that "unspecified behavior"). Your second quotation is irrelevant, since the two members in my union are of the same size, so both members "completely fill" the union (and this would still hold even if I declared a single char, because the biggest member is stored first).Skeens
yes the second quote is actually irrelevant. +1 for the addition of the pedantic version I just see now;)Growl
That's a good idea. Unfortunately, the number of functions to duplicate in this case is a bit too high (8), and will hamper future code maintenance. The basic idea was to embed the small differences due to endianess into macro (or inlined functions), keeping the code leans and common to all platforms.Niobous
Better not call your macros BIG_ENDIAN and LITTLE_ENDIAN -- <endian.h> on Linux/*BSD defines macros by such names, and they will therefore both be always defined if you happen to include <endian.h>.Axial
@LauriNurmi: woa, well spotted; I'll change them to something else.Skeens
N
24

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.

Niobous answered 5/2, 2012 at 23:18 Comment(2)
Writing for other people who find this answer useful. gcc supports __BYTE_ORDER__ from about 4.6 and clang from 3.2Bastinado
You can write static_assert(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__); which works on GCC, and will otherwise fail on compilers that don't define these.Piezoelectricity
S
19

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)

Skeens answered 23/1, 2012 at 21:43 Comment(9)
The value of a union member other than the last one stored into is an unspecified behavior in C.Growl
@ouah: the C standard knows nothing about endianness, so we are already going out of the standard domain and working on implementation-specific behavior (and I don't think you'll ever find a compiler implementing unions differently or an optimizer messing with them). Although, I agree that the other "classic method" (cast of the pointer to char *) does not exhibit UB problems due to the exceptions to the aliasing rules.Skeens
@ouah: also, §6.7.2.1 doesn't mention UB, it just says that "The value of at most one of the members can be stored in a union object at any time"; also, I dare to say that §6.7.2.1 ¶14 implicitly allows the use of unions as a replacement for that cast, since "A pointer to a union object, suitably converted, points to each of its members [...] and vice versa.". So, &u.i = &u = &u.c (with the appropriate casts), thus u.c[0] = (*(&u.c))[0]=*((char *)&u.i), which is as legal as the "other method".Skeens
In C99, Annex J (non-normative) "J.1 Unspecified behavior. The following are unspecified: The value of a union member other than the last one stored into (6.2.6.1)." and 6.2.6.1p7 says "When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values."Growl
@ouah: the first one is solved by working on §6.7.2.1 ¶14 as I already wrote before (it's still unspecified behavior, but exactly as it is the cast - and hey, that code is there to understand exactly how the compiler implements that "unspecified behavior"). Your second quotation is irrelevant, since the two members in my union are of the same size, so both members "completely fill" the union (and this would still hold even if I declared a single char, because the biggest member is stored first).Skeens
yes the second quote is actually irrelevant. +1 for the addition of the pedantic version I just see now;)Growl
That's a good idea. Unfortunately, the number of functions to duplicate in this case is a bit too high (8), and will hamper future code maintenance. The basic idea was to embed the small differences due to endianess into macro (or inlined functions), keeping the code leans and common to all platforms.Niobous
Better not call your macros BIG_ENDIAN and LITTLE_ENDIAN -- <endian.h> on Linux/*BSD defines macros by such names, and they will therefore both be always defined if you happen to include <endian.h>.Axial
@LauriNurmi: woa, well spotted; I'll change them to something else.Skeens
I
17

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?

Inshrine answered 23/1, 2012 at 22:13 Comment(4)
Good point. Unfortunately, i cannot change this convention now, since the code has been in use for quite some time now, and it needs to remain compatible with existing user data.Niobous
@Niobous - Ah. In that case you're going to have to run a build-time check with something like autoconf to define the macro for you, or settle for a runtime solution.Inshrine
This only works if don't have any 64-bit data types. At least on linux, htonl returns a uint32_t, not an unsigned long, so even on 64-bit platforms, it should operate on 32-bit values. That's usually the desired behavior for the function to work correctly with existing networking code.Hester
@BrianMcFarland - Yeah, when I looked through the manpages I thought, "I remember this function family being a lot more useful the last time I encountered them." I suppose people will usually have to implement their own platform-dependent wrappers. I wonder if any compilers optimize various runtime-endian-check idioms into compile-time constants to reduce code execution paths?Inshrine
V
11

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].

Viviparous answered 24/1, 2012 at 1:26 Comment(3)
This particular example doesn't compile. Maybe it is meant to be used on C++ ? It seems it doesn't respect C limitations on var initialisation.Niobous
It's written in the current C language, not C89. You're probably using a backwards compiler like MSVC, in which case you'll need to adapt it a bit..Viviparous
Indeed, i'm testing the source code with both MSVC and GCC. The code should work with both.Niobous
W
9

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;

}
Wagonage answered 23/1, 2012 at 21:43 Comment(0)
T
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!

Tevis answered 23/1, 2012 at 21:52 Comment(2)
I've heard this method may cause problems in a cross-compile environment, where the endianness of the target system is different from the endianness of the build system.Crain
You should provide an example of using Autotools' AC_C_BIGENDIAN. This was the first Stack Overflow reference for a search of "Autoconf AC_C_BIGENDIAN", but it lacks the example I was hoping for.Loveridge
C
4

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;
}
Chrono answered 15/6, 2017 at 12:47 Comment(0)
C
4

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;
}
Caye answered 6/8, 2018 at 12:52 Comment(0)
R
4

My GCC version is 9.3.0, it's configured to support powerpc64 platform, and I've tested it and verified that it supports the following macros logic:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
......
#endif
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
.....
#endif
Reluctant answered 1/6, 2021 at 10:39 Comment(4)
I remember using this method initially. The problem is : it tends to work on system I test on, but it may not work for some other systems. Depending on your portability objectives, it might be a liability or not. In my case, since I don't control which systems my software run, I have to target maximum portability. It made this macro check undesirable. I instead use a runtime test, which is effectively transformed into a compile-time constant by the compiler.Niobous
@ClockZHONG note that the bold here is two underscores (ie __) so read LITTLE_ENDIAN as __LITTLE_ENDIAN__Clansman
@Niobous I remember in old versions PPC toolchain, they use LITTLE_ENDIAN & BIG_ENDIAN to judge the platform endian configuration, but for newer version GCC, it seems __BYTE_ORDER__&__ORDER_LITTLE_ENDIAN__&__ORDER_BIG_ENDIAN__ is a more compatible solution, you could check it here:gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.htmlReluctant
@Clansman Thanks! I know how to show original code style in the comment now, I really didn't notice it before your remind.Reluctant
C
4

As of C++20, no more hacks or compiler extensions are necessary.

https://en.cppreference.com/w/cpp/types/endian

std::endian (Defined in header <bit>)

enum class endian
{
    little = /*implementation-defined*/,
    big    = /*implementation-defined*/,
    native = /*implementation-defined*/
};
  • If all scalar types are little-endian, std::endian::native equals std::endian::little

  • If all scalar types are big-endian, std::endian::native equals std::endian::big

Chamberlin answered 5/10, 2021 at 19:16 Comment(0)
H
2

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.

Hesler answered 23/1, 2012 at 21:43 Comment(0)
G
1

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

Growl answered 23/1, 2012 at 21:43 Comment(0)
W
1

While on the hunt for the same problem, I stumbled upon this: https://gist.github.com/jtbr/7a43e6281e6cca353b33ee501421860c

I have attached the copy of the above linked code and it may be outdated if the author updates it.

/** 
* @file   endianness.h
* @brief  Convert Endianness of shorts, longs, long longs, regardless of architecture/OS
*
* Defines (without pulling in platform-specific network include headers):
* bswap16, bswap32, bswap64, ntoh16, hton16, ntoh32 hton32, ntoh64, hton64
*
* Should support linux / macos / solaris / windows.
* Supports GCC (on any platform, including embedded), MSVC2015, and clang,
* and should support intel, solaris, and ibm compilers as well.
*
* Copyright 2020 github user jtbr, Released under MIT license
*
* SPDX-License-Identifier: MIT OR Apache-2.0
*/

#ifndef ENDIANNESS_H_
#define ENDIANNESS_H_

#include <stdlib.h>
#include <stdint.h>
#ifdef __cplusplus
#include <cstring> // for memcpy
#endif

/* Detect platform endianness at compile time */

// If boost were available on all platforms, could use this instead to detect endianness
// #include <boost/predef/endian.h>

// When available, these headers can improve platform endianness detection
#ifdef __has_include // C++17, supported as extension to C++11 in clang, GCC 5+, vs2015
#  if __has_include(<endian.h>)
#    include <endian.h> // gnu libc normally provides, linux
#  elif __has_include(<machine/endian.h>)
#    include <machine/endian.h> //open bsd, macos
#  elif __has_include(<sys/param.h>)
#    include <sys/param.h> // mingw, some bsd (not open/macos)
#  elif __has_include(<sys/isadefs.h>)
#    include <sys/isadefs.h> // solaris
#  endif
#endif

#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
#  if (defined(__BYTE_ORDER__)  && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \
 (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) || \
     (defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN) || \
     (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) || \
 (defined(__sun) && defined(__SVR4) && defined(_BIG_ENDIAN)) || \
 defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \
 defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) || \
 defined(_M_PPC)
#    define __BIG_ENDIAN__
#  elif (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || /* gcc */\
 (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) /* linux header */ || \
     (defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN) || \
     (defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN) /* mingw header */ ||  \
 (defined(__sun) && defined(__SVR4) && defined(_LITTLE_ENDIAN)) || /* solaris */ \
 defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || \
 defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \
 defined(_M_IX86) || defined(_M_X64) || defined(_M_IA64) || /* msvc for intel processors */ \
 defined(_M_ARM) /* msvc code on arm executes in little endian mode */
#    define __LITTLE_ENDIAN__
#  endif
#endif

#if !defined(__LITTLE_ENDIAN__) & !defined(__BIG_ENDIAN__)
#  error "UNKNOWN Platform / endianness. Configure endianness checks for this platform or set explicitly."
#endif

#if defined(bswap16) || defined(bswap32) || defined(bswap64) || defined(bswapf) || defined(bswapd)
#  error "unexpected define!" // freebsd may define these; probably just need to undefine them
#endif

/* Define byte-swap functions, using fast processor-native built-ins where possible */
#if defined(_MSC_VER) // needs to be first because msvc doesn't short-circuit after failing defined(__has_builtin)
#  define bswap16(x)     _byteswap_ushort((x))
#  define bswap32(x)     _byteswap_ulong((x))
#  define bswap64(x)     _byteswap_uint64((x))
#elif (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
#  define bswap16(x)     __builtin_bswap16((x))
#  define bswap32(x)     __builtin_bswap32((x))
#  define bswap64(x)     __builtin_bswap64((x))
#elif defined(__has_builtin) && __has_builtin(__builtin_bswap64)  /* for clang; gcc 5 fails on this and && shortcircuit fails; must be after GCC check */
#  define bswap16(x)     __builtin_bswap16((x))
#  define bswap32(x)     __builtin_bswap32((x))
#  define bswap64(x)     __builtin_bswap64((x))
#else
/* even in this case, compilers often optimize by using native instructions */
static inline uint16_t bswap16(uint16_t x) {
        return ((( x  >> 8 ) & 0xffu ) | (( x  & 0xffu ) << 8 ));
    }
static inline uint32_t bswap32(uint32_t x) {
    return ((( x & 0xff000000u ) >> 24 ) |
            (( x & 0x00ff0000u ) >> 8  ) |
            (( x & 0x0000ff00u ) << 8  ) |
            (( x & 0x000000ffu ) << 24 ));
}
static inline uint64_t bswap64(uint64_t x) {
    return ((( x & 0xff00000000000000ull ) >> 56 ) |
            (( x & 0x00ff000000000000ull ) >> 40 ) |
            (( x & 0x0000ff0000000000ull ) >> 24 ) |
            (( x & 0x000000ff00000000ull ) >> 8  ) |
            (( x & 0x00000000ff000000ull ) << 8  ) |
            (( x & 0x0000000000ff0000ull ) << 24 ) |
            (( x & 0x000000000000ff00ull ) << 40 ) |
            (( x & 0x00000000000000ffull ) << 56 ));
}
#endif

//! Byte-swap 32-bit float
static inline float bswapf(float f) {
#ifdef __cplusplus
static_assert(sizeof(float) == sizeof(uint32_t), "Unexpected float format");
/* Problem: de-referencing float pointer as uint32_t breaks strict-aliasing rules for C++ and C, even if it normally works
 *   uint32_t val = bswap32(*(reinterpret_cast<const uint32_t *>(&f)));
 *   return *(reinterpret_cast<float *>(&val));
 */
// memcpy approach is guaranteed to work in C & C++ and fn calls should be optimized out:
uint32_t asInt;
std::memcpy(&asInt, reinterpret_cast<const void *>(&f), sizeof(uint32_t));
asInt = bswap32(asInt);
std::memcpy(&f, reinterpret_cast<void *>(&asInt), sizeof(float));
return f;
#else
_Static_assert(sizeof(float) == sizeof(uint32_t), "Unexpected float format");
// union approach is guaranteed to work in C99 and later (but not in C++, though in practice it normally will):
union { uint32_t asInt; float asFloat; } conversion_union;
conversion_union.asFloat = f;
conversion_union.asInt = bswap32(conversion_union.asInt);
return conversion_union.asFloat;
#endif
}

//! Byte-swap 64-bit double
static inline double bswapd(double d) {
#ifdef __cplusplus
static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double format");
uint64_t asInt;
std::memcpy(&asInt, reinterpret_cast<const void *>(&d), sizeof(uint64_t));
asInt = bswap64(asInt);
std::memcpy(&d, reinterpret_cast<void *>(&asInt), sizeof(double));
return d;
#else
_Static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double format");
union { uint64_t asInt; double asDouble; } conversion_union;
conversion_union.asDouble = d;
conversion_union.asInt = bswap64(conversion_union.asInt);
return conversion_union.asDouble;
#endif
}


/* Define network - host byte swaps as needed depending upon platform endianness */
// (note that network order is big endian)

#if defined(__LITTLE_ENDIAN__)
#  define ntoh16(x)     bswap16((x))
#  define hton16(x)     bswap16((x))
#  define ntoh32(x)     bswap32((x))
#  define hton32(x)     bswap32((x))
#  define ntoh64(x)     bswap64((x))
#  define hton64(x)     bswap64((x))
#  define ntohf(x)      bswapf((x))
#  define htonf(x)      bswapf((x))
#  define ntohd(x)      bswapd((x))
#  define htond(x)      bswapd((x))
#elif defined(__BIG_ENDIAN__)
#  define ntoh16(x)     (x)
#  define hton16(x)     (x)
#  define ntoh32(x)     (x)
#  define hton32(x)     (x)
#  define ntoh64(x)     (x)
#  define hton64(x)     (x)
#  define ntohf(x)      (x)
#  define htonf(x)      (x)
#  define ntohd(x)      (x)
#  define htond(x)      (x)
#  else
#    warning "UNKNOWN Platform / endianness; network / host byte swaps not defined."
#endif

#endif //ENDIANNESS_H_

It defines __BIG_ENDIAN__ or __LITTLE_ENDIAN__, if neither could be determined it throws an error via #error pre-processor command.

Waterloo answered 10/3 at 17:51 Comment(0)
S
0

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)]; }
Schorl answered 1/2, 2019 at 16:51 Comment(0)
W
0

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.

Whalen answered 28/5, 2019 at 18:20 Comment(0)
B
0

I provided a general approach in C with no preprocessor, but only runtime that compute endianess for every C type.

the output if this on my Linux x86_64 architecture is:

fabrizio@toshibaSeb:~/git/pegaso/scripts$ gcc -o sizeof_endianess sizeof_endianess.c 
fabrizio@toshibaSeb:~/git/pegaso/scripts$ ./sizeof_endianess 
INTEGER TYPE  | signed  |  unsigned  | 0x010203...             | Endianess
--------------+---------+------------+-------------------------+--------------
int           |  4      |      4     | 04 03 02 01             | little
char          |  1      |      1     | -                       | -
short         |  2      |      2     | 02 01                   | little
long int      |  8      |      8     | 08 07 06 05 04 03 02 01 | little
long long int |  8      |      8     | 08 07 06 05 04 03 02 01 | little
--------------+---------+------------+-------------------------+--------------
FLOATING POINT| size    |
--------------+---------+
float         |  4
double        |  8
long double   | 16

Get source at: https://github.com/bzimage-it/pegaso/blob/master/scripts/sizeof_endianess.c

This is a more general approach is to not detect endianess at compilation time (not possibile) nor assume any endianess escludes another one. In fact is important to remark that endianess is not a concept of the architecture/processor but regards single type. As argued by @Christoph at https://mcmap.net/q/20760/-how-can-i-detect-endianness-on-a-system-where-all-primitive-integer-sizes-are-the-same PDP-11 for example can have different endianess at the same time.

The approach consist to set an integer to be x = 0x010203... as long is it, then print them looking at casted-at-single-byte incrementing the address by one.

Can somebody test it please in a big endian and/or mixed endianess ?

Birgitbirgitta answered 20/9, 2022 at 13:7 Comment(0)
T
-1

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.

Trudietrudnak answered 18/7, 2017 at 20:23 Comment(2)
An important question is, can this version be optimized away by compiler ? Answer is : mostly yes, although not as well as a numerical version : godbolt.org/g/GhtEYWNiobous
Interesting idea, yet can violate alignment requirements of uint16_t. (it is UB code for some embedded processors).Tatar
M
-6
#define BIG_ENDIAN ((1 >> 1 == 0) ? 0 : 1)
Motte answered 24/1, 2012 at 8:53 Comment(6)
How does this detect endianness?Principality
with the byte shift. 1 >> 1 is 0 on a little endian arch. (the bit get lost after shifting, thus it evals to 0). one could warp this into an enum for readability.Motte
Great, but the same will happen on any endianness. Endianness is how the data is stored, not how the shift is done.Principality
little endian: 1 is 0000 00001, after shift it's 0000 0000 big endian: 1 is 0001 0000, after shift it's 0000 1000 Am I wrong ?Motte
That's how bytes are stored in memory, but manipulation is not done byte-wise, it is done on the number as a whole which is endianness-agnostic.Principality
same comment, i'm afraid it does not work. For unsigned numbers, ">> 1" is equal to "/ 2", whatever endianess.Niobous

© 2022 - 2024 — McMap. All rights reserved.