153

I'm looking for a way to reliably determine whether C++ code is being compiled in 32 vs 64 bit. We've come up with what we think is a reasonable solution using macros, but was curious to know if people could think of cases where this might fail or if there is a better way to do this. Please note we are trying to do this in a cross-platform, multiple compiler environment.

#if ((ULONG_MAX) == (UINT_MAX))
# define IS32BIT
#else
# define IS64BIT
#endif

#ifdef IS64BIT
DoMy64BitOperation()
#else
DoMy32BitOperation()
#endif

Thanks.

osgx
  • 90,338
  • 53
  • 357
  • 513
Joe Corkery
  • 2,564
  • 3
  • 18
  • 26
  • 9
    If you really care what the word-size of your architecture is, then don't overlook the possibility that it's neither 32 nor 64-bit. There are 16 and 128-bit architectures out there, you know. – alex tingle Oct 01 '09 at 19:07
  • What is the difference between the 64 bit and the 32 bit operation? – peterchen Oct 01 '09 at 19:49
  • 2
    You really shouldn't conditionalize this on the word-width of the target platform. Instead, use the size of the relevant datatypes directly to determine what to do. `stdint.h` might be your friend, or you may need to develop some appropriate typedefs of your own. – Phil Miller Oct 19 '09 at 17:27
  • This test doesn't seem to work on Visual Studio 2008 SP1. It gets stuck on "IS64BIT" for both 32-bit and 64-bit. – Contango Sep 09 '12 at 10:02

16 Answers16

118

Unfortunately there is no cross platform macro which defines 32 / 64 bit across the major compilers. I've found the most effective way to do this is the following.

First I pick my own representation. I prefer ENVIRONMENT64 / ENVIRONMENT32. Then I find out what all of the major compilers use for determining if it's a 64 bit environment or not and use that to set my variables.

// Check windows
#if _WIN32 || _WIN64
#if _WIN64
#define ENVIRONMENT64
#else
#define ENVIRONMENT32
#endif
#endif

// Check GCC
#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENVIRONMENT64
#else
#define ENVIRONMENT32
#endif
#endif

Another easier route is to simply set these variables from the compiler command line.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 4
    well, there exist other compilers besides GCC and VS. For example QNX and GHS come to mind (although I suspect QNX has similar build-time defines to GCC). Also you forgot MIPS64 and IA64 architectures in your GCC check – Rom Oct 01 '09 at 18:35
  • 15
    @Rom, definitely more than 2 compilers and architectures. This is just meant to be a sample of how to approach this problem, not a complete solution. – JaredPar Oct 01 '09 at 18:38
  • 1
    Usually the "notes for porting this application/library to a new platform" will contain a list of all the header files that need clauses added in order to support a new compiler... – Steve Jessop Oct 01 '09 at 18:43
  • 3
    I say "usually". "Ideally" is probably more realistic. – Steve Jessop Oct 01 '09 at 18:43
  • 1
    Thanks, this is pretty close to what we were looking for. We'll add additional compiler options as we encounter them, but this covers 90% of cases right now. – Joe Corkery Oct 02 '09 at 14:52
  • _ILP32 and _LP64 on Solaris, although to be pedantic this macro states the size of Int Long and Pointer rather than true 32/64. – Chris Huang-Leaver Oct 22 '09 at 15:16
  • 10
    I think you should use "#if defined(_WIN32_) || defined(_WIN64)" etc – KindDragon Dec 13 '13 at 13:03
  • This page is quite helpful in determining which compiler defines to use: http://nadeausoftware.com/articles/2012/02/c_c_tip_how_detect_processor_type_using_compiler_predefined_macros#x86andx8664 – Nathan Osman Feb 27 '15 at 01:32
  • 5
    `#if _WIN32 || _WIN64` … `#elif __GNUC__` … `#else` `# error "Missing feature-test macro for 32/64-bit on this compiler."`? – Davislor Nov 29 '15 at 20:24
  • Just tested it and got 32 for x64 build. The reason I need this is to set correct information in the file metadata, so it's possible to distinguish x32 vs x64 build. – Tomáš Zato Jan 26 '17 at 23:05
  • what if -m32 flag is added? – habi Mar 31 '22 at 14:47
106
template<int> void DoMyOperationHelper();

template<> void DoMyOperationHelper<4>() 
{
  // do 32-bits operations
}

template<> void DoMyOperationHelper<8>() 
{
  // do 64-bits operations
}

// helper function just to hide clumsy syntax
inline void DoMyOperation() { DoMyOperationHelper<sizeof(size_t)>(); }

int main()
{
  // appropriate function will be selected at compile time 
  DoMyOperation(); 

  return 0;
}
Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
  • 2
    What happens if the size_t is neither 4 nor 8? – Jesper Oct 01 '09 at 20:17
  • 17
    @Jesper, Then you'll get link error in the sample above. Or you could implement DoMyOperation for that case – Kirill V. Lyadvinsky Oct 01 '09 at 20:40
  • 1
    Slick use of templates, and kudos for testing what matters (the size of some particular type) rather than a correlate. – Phil Miller Oct 19 '09 at 17:25
  • 2
    Careful with using size_t for this. You can have issues where it doesn't correspond to the pointer size for instance (eg on platforms with more than one pointer size). – Logan Capaldo Oct 22 '09 at 12:03
  • 9
    Standard says that size of `size_t` is large enough to hold size of any allocated object in system. Usually it is what you want to know while conditional compiling. If it is not what you want, you could use this snippet with some other type instead of `size_t`. For instance, it could be `void*`. – Kirill V. Lyadvinsky Oct 22 '09 at 12:35
49

Unfortunately, in a cross platform, cross compiler environment, there is no single reliable method to do this purely at compile time.

  • Both _WIN32 and _WIN64 can sometimes both be undefined, if the project settings are flawed or corrupted (particularly on Visual Studio 2008 SP1).
  • A project labelled "Win32" could be set to 64-bit, due to a project configuration error.
  • On Visual Studio 2008 SP1, sometimes the intellisense does not grey out the correct parts of the code, according to the current #define. This makes it difficult to see exactly which #define is being used at compile time.

Therefore, the only reliable method is to combine 3 simple checks:

  • 1) Compile time setting, and;
  • 2) Runtime check, and;
  • 3) Robust compile time checking.

Simple check 1/3: Compile time setting

Choose any method to set the required #define variable. I suggest the method from @JaredPar:

// Check windows
#if _WIN32 || _WIN64
   #if _WIN64
     #define ENV64BIT
  #else
    #define ENV32BIT
  #endif
#endif

// Check GCC
#if __GNUC__
  #if __x86_64__ || __ppc64__
    #define ENV64BIT
  #else
    #define ENV32BIT
  #endif
#endif

Simple check 2/3: Runtime check

In main(), double check to see if sizeof() makes sense:

#if defined(ENV64BIT)
    if (sizeof(void*) != 8)
    {
        wprintf(L"ENV64BIT: Error: pointer should be 8 bytes. Exiting.");
        exit(0);
    }
    wprintf(L"Diagnostics: we are running in 64-bit mode.\n");
#elif defined (ENV32BIT)
    if (sizeof(void*) != 4)
    {
        wprintf(L"ENV32BIT: Error: pointer should be 4 bytes. Exiting.");
        exit(0);
    }
    wprintf(L"Diagnostics: we are running in 32-bit mode.\n");
#else
    #error "Must define either ENV32BIT or ENV64BIT".
#endif

Simple check 3/3: Robust compile time checking

The general rule is "every #define must end in a #else which generates an error".

#if defined(ENV64BIT)
    // 64-bit code here.
#elif defined (ENV32BIT)
    // 32-bit code here.
#else
    // INCREASE ROBUSTNESS. ALWAYS THROW AN ERROR ON THE ELSE.
    // - What if I made a typo and checked for ENV6BIT instead of ENV64BIT?
    // - What if both ENV64BIT and ENV32BIT are not defined?
    // - What if project is corrupted, and _WIN64 and _WIN32 are not defined?
    // - What if I didn't include the required header file?
    // - What if I checked for _WIN32 first instead of second?
    //   (in Windows, both are defined in 64-bit, so this will break codebase)
    // - What if the code has just been ported to a different OS?
    // - What if there is an unknown unknown, not mentioned in this list so far?
    // I'm only human, and the mistakes above would break the *entire* codebase.
    #error "Must define either ENV32BIT or ENV64BIT"
#endif

Update 2017-01-17

Comment from @AI.G:

4 years later (don't know if it was possible before) you can convert the run-time check to compile-time one using static assert: static_assert(sizeof(void*) == 4);. Now it's all done at compile time :)

Appendix A

Incidentially, the rules above can be adapted to make your entire codebase more reliable:

  • Every if() statement ends in an "else" which generates a warning or error.
  • Every switch() statement ends in a "default:" which generates a warning or error.

The reason why this works well is that it forces you to think of every single case in advance, and not rely on (sometimes flawed) logic in the "else" part to execute the correct code.

I used this technique (among many others) to write a 30,000 line project that worked flawlessly from the day it was first deployed into production (that was 12 months ago).

Contango
  • 76,540
  • 58
  • 260
  • 305
  • `sizeof(void*)` is it resolved at compile time or run-time? if it is at compile time then at run-time the check always will be `if(8!=8){...}`. – Ameen Jan 03 '15 at 18:56
  • @ameen It's resolved at run-time. The aim of this check is to ensure that the program exits with an appropriate error if the bitness is not what is expected. This means that the developer can fix this error immediately, rather than trying to diagnose subtle bugs that pop up later on. – Contango May 31 '15 at 13:52
  • 3
    4 years later (don't know if it was possible before) you can convert the run-time check to compile-time one using static assert: `static_assert(sizeof(void*) == 4);`. Now it's all done at compile time :) – Al.G. Jan 07 '17 at 10:27
  • @AI.G Excellent comment, it's so good I updated the answer to add it. Thanks! – Contango Jan 07 '17 at 21:37
  • 1
    `static_assert(sizeof(void*) * CHAR_BIT == 32)` is more expressive and technically correct (although I don't know any architecture where bytes have different amount of bits than 8) – Xeverous Mar 04 '19 at 14:27
  • 1
    See also [my answer below](https://stackoverflow.com/a/61063780/201787) that combines this excellent answer with "[Better Macros, Better Flags](https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/)" from Fluent C++. – metal Apr 06 '20 at 15:56
44

You should be able to use the macros defined in stdint.h. In particular INTPTR_MAX is exactly the value you need.

#include <cstdint>
#if INTPTR_MAX == INT32_MAX
    #define THIS_IS_32_BIT_ENVIRONMENT
#elif INTPTR_MAX == INT64_MAX
    #define THIS_IS_64_BIT_ENVIRONMENT
#else
    #error "Environment not 32 or 64-bit."
#endif

Some (all?) versions of Microsoft's compiler don't come with stdint.h. Not sure why, since it's a standard file. Here's a version you can use: http://msinttypes.googlecode.com/svn/trunk/stdint.h

alex tingle
  • 6,920
  • 3
  • 25
  • 29
  • 4
    Why no stdint.h for Microsoft? Because it was introduced with the C99 standard, and Microsoft seems to have an active aversion to implementing even the easiest of stuff from C99. Even the easy library stuff that requires no compiler change. Even the stuff that's already being done when compiling for C++ (like declarations after statements). I know it needs testing, etc., but I also know that MS gets (or once got) a fair chunk of its library from Dinkumware/Plauger, and Dinkumware's had the C99 library stuff around for years. – Michael Burr Oct 01 '09 at 19:19
  • 2
    VC++2010 (beta 1, anyway) has `` and ``. As for the present state of affairs - VC++ library originates from Dinkumware (still does - TR1 was taken from there as well), but from what I recall reading on VCBlog, it undergoes a fairly significant refactoring to compile cleanly with `/clr`, work with all MSVC non-standard types like `__int64`, and so on - which is why it's not as simple as just taking it and putting it into next compiler version. – Pavel Minaev Oct 01 '09 at 19:44
  • 3
    This led me to the correct answer, but I think you should compare to UINT64_MAX not INT64_MAX. I used SIZE_MAX == UINT64_MAX -- prob the same – Arno Duvenhage Jan 19 '16 at 16:52
  • 1
    @ArnoDuvenhage: `intptr_t` is guaranteed to be a signed type, so this answer is correct. Your ways would also work, and yeah I would normally have gone for unsigned types since I think of pointers as unsigned integers if I'm going to do anything tricky with them (other than redo sign-extension to make a canonical x86-64 pointer after using the high bits for something else). – Peter Cordes May 16 '22 at 14:50
15

That won't work on Windows for a start. Longs and ints are both 32 bits whether you're compiling for 32 bit or 64 bit windows. I would think checking if the size of a pointer is 8 bytes is probably a more reliable route.

mattnewport
  • 13,728
  • 2
  • 35
  • 39
  • 2
    Unfortunately sizeof is prohibited in #if directive (if you think about it preprocessor has no way to tell) – EFraim Oct 01 '09 at 18:25
  • Yep, that's why I left it at suggesting checking the size of a pointer rather than using sizeof - I can't think of a portable way to do it off the top of my head... – mattnewport Oct 01 '09 at 18:27
  • 3
    Question doesn't (yet) say it *has* to be done at pre-processor time. Many/most compilers with optimisation on will do a decent job of eliminating dead code, even if you "leave it until run time" with a test like `sizeof(void*) == 8 ? Do64Bit() : Do32Bit();`. That could still leave an unused function in the binary, but the expression is likely compiled just to a call to the "right" function. – Steve Jessop Oct 01 '09 at 18:41
  • 1
    @onebyone that solves the problem of function calls, but what if I want to declare a variable a different type based on platform, that would need to be done at preprocessor unless you want to declare multiple variables and use them based on an if statement (which would also be optimized out if they're unused, but wouldn't be very pleasant in the code) – Falaina Oct 01 '09 at 18:45
  • 1
    Then you're right, a constant expression in a conditional is no good. Kirill's approach can do what you want, though: `template struct Thing; template<> struct Thing<4> { typedef uint32_t type; }; template<> struct Thing<8> { typedef uint64_t type; }; typedef Thing::type thingtype;` – Steve Jessop Oct 01 '09 at 18:50
11

You could do this:

#if __WORDSIZE == 64
char *size = "64bits";
#else
char *size = "32bits";
#endif
Anoop
  • 111
  • 1
  • 2
  • 1
    In many programming environments for C and C-derived languages on 64-bit machines, "int" variables are still 32 bits wide, but long integers and pointers are 64 bits wide. These are described as having an LP64 data model. http://www.unix.org/version2/whatsnew/lp64_wp.html – Hermes Jun 05 '14 at 21:46
8
Try this:
#ifdef _WIN64
// 64 bit code
#elif _WIN32
// 32 bit code
#else
   if(sizeof(void*)==4)

       // 32 bit code
   else 

       // 64 bit code   
#endif
jxh
  • 69,070
  • 8
  • 110
  • 193
emj8321
  • 116
  • 1
  • 5
  • 7
    This code is not correct. On 64-bit both _WIN32 and _WIN64 are defined. If you turn it around (first check for _WIN64) it works of course. – BertR Jul 10 '12 at 14:26
6

Below code works fine for most current environments:

  #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) &&     !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
    #define IS64BIT 1
 #else
    #define IS32BIT 1
#endif
Alex Byrth
  • 1,328
  • 18
  • 23
  • 6
    Note that ``_WIN64`` requires you have already included ````. With Visual C++, it's better to use the built-in compiler defines: ``_M_IX86``, ``_M_X64``, ``_M_ARM``, ``_M_ARM64``, etc. – Chuck Walbourn Aug 03 '18 at 03:36
  • For PowerPC, I believe you need to check for `__ppc64__`, `__powerpc64__`, and `_ARCH_PPC64`. That catches AIX and other platforms, too. – jww Nov 26 '18 at 07:02
4

"Compiled in 64 bit" is not well defined in C++.

C++ sets only lower limits for sizes such as int, long and void *. There is no guarantee that int is 64 bit even when compiled for a 64 bit platform. The model allows for e.g. 23 bit ints and sizeof(int *) != sizeof(char *)

There are different programming models for 64 bit platforms.

Your best bet is a platform specific test. Your second best, portable decision must be more specific in what is 64 bit.

peterchen
  • 40,917
  • 20
  • 104
  • 186
4

Your approach was not too far off, but you are only checking whether long and int are of the same size. Theoretically, they could both be 64 bits, in which case your check would fail, assuming both to be 32 bits. Here is a check that actually checks the size of the types themselves, not their relative size:

#if ((UINT_MAX) == 0xffffffffu)
    #define INT_IS32BIT
#else
    #define INT_IS64BIT
#endif
#if ((ULONG_MAX) == 0xfffffffful)
    #define LONG_IS32BIT
#else
    #define LONG_IS64BIT
#endif

In principle, you can do this for any type for which you have a system defined macro with the maximal value.

Note, that the standard requires long long to be at least 64 bits even on 32 bit systems.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • One thing to be noted, for the UINT_MAX and ULONG_MAX to be defined, you probably want to have `#include ` somewhere before your `#if` tests. – Alexis Wilke Apr 04 '15 at 04:52
3

People already suggested methods that will try to determine if the program is being compiled in 32-bit or 64-bit.

And I want to add that you can use the c++11 feature static_assert to make sure that the architecture is what you think it is ("to relax").

So in the place where you define the macros:

#if ...
# define IS32BIT
  static_assert(sizeof(void *) == 4, "Error: The Arch is not what I think it is")
#elif ...
# define IS64BIT
  static_assert(sizeof(void *) == 8, "Error: The Arch is not what I think it is")
#else
# error "Cannot determine the Arch"
#endif
Ameen
  • 1,747
  • 3
  • 16
  • 31
  • `static_assert(sizeof(void*) * CHAR_BIT == 32)` is more expressive and technically correct (although I don't know any architecture where bytes have different amount of bits than 8) – Xeverous Mar 04 '19 at 14:28
3

Borrowing from Contango's excellent answer above and combining it with "Better Macros, Better Flags" from Fluent C++, you can do:

// Macro for checking bitness (safer macros borrowed from 
// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/)
#define MYPROJ_IS_BITNESS( X ) MYPROJ_IS_BITNESS_PRIVATE_DEFINITION_##X()

// Bitness checks borrowed from https://stackoverflow.com/a/12338526/201787
#if _WIN64 || ( __GNUC__ && __x86_64__ )
#    define MYPROJ_IS_BITNESS_PRIVATE_DEFINITION_64() 1
#    define MYPROJ_IS_BITNESS_PRIVATE_DEFINITION_32() 0
#    define MYPROJ_IF_64_BIT_ELSE( x64, x86 ) (x64)
    static_assert( sizeof( void* ) == 8, "Pointer size is unexpected for this bitness" );
#elif _WIN32 || __GNUC__
#    define MYPROJ_IS_BITNESS_PRIVATE_DEFINITION_64() 0
#    define MYPROJ_IS_BITNESS_PRIVATE_DEFINITION_32() 1
#    define MYPROJ_IF_64_BIT_ELSE( x64, x86 ) (x86)
    static_assert( sizeof( void* ) == 4, "Pointer size is unexpected for this bitness" );
#else
#    error "Unknown bitness!"
#endif

Then you can use it like:

#if MYPROJ_IS_BITNESS( 64 )
    DoMy64BitOperation()
#else
    DoMy32BitOperation()
#endif

Or using the extra macro I added:

MYPROJ_IF_64_BIT_ELSE( DoMy64BitOperation(), DoMy32BitOperation() );
metal
  • 6,202
  • 1
  • 34
  • 49
2

Here are a few more ways to do what you want in modern C++.

You can create a variable that defines the number of system bits:

static constexpr size_t sysbits = (CHAR_BIT * sizeof(void*));

And then in C++17 you can do something like:

void DoMy64BitOperation() { 
    std::cout << "64-bit!\n"; 
}

void DoMy32BitOperation() { 
    std::cout << "32-bit!\n"; 
}

inline void DoMySysBitOperation() 
{ 
    if constexpr(sysbits == 32)
        DoMy32BitOperation();
    else if constexpr(sysbits == 64)
        DoMy64BitOperation();
    /*else - other systems. */
}

Or in C++20:

template<void* = nullptr>
// template<int = 32>  // May be clearer, pick whatever you like.
void DoMySysBitOperation()
    requires(sysbits == 32)
{
    std::cout << "32-bit!\n"; 
}

template<void* = nullptr>
// template<int = 64>
void DoMySysBitOperation()
    requires(sysbits == 64)
{
    std::cout << "64-bit!\n"; 
}

template<void* = nullptr>
void DoMySysBitOperation()
    /* requires(sysbits == OtherSystem) */
{
    std::cout << "Unknown System!\n"; 
}

The template<...> is usually not needed, but since those functions will have the same mangling name, we must enforce the compiler to pick the correct ones. Also, template<void* = nullptr> may be confusing ( The other template may be better and more logically correct ), I only used it as a workaround to satisfy the compiler name mangling.

therealcain
  • 175
  • 1
  • 4
  • 10
1

If you can use project configurations in all your environments, that would make defining a 64- and 32-bit symbol easy. So you'd have project configurations like this:

32-bit Debug
32-bit Release
64-bit Debug
64-bit Release

EDIT: These are generic configurations, not targetted configurations. Call them whatever you want.

If you can't do that, I like Jared's idea.

Jon Seigel
  • 12,251
  • 8
  • 58
  • 92
  • Or combine the two: auto-detect the configuration on the compilers you know about, but fall back to looking at a #define specified in the project/command-line/whatever on unrecognised compilers. – Steve Jessop Oct 01 '09 at 18:45
  • 4
    How is your VisualStudio-specific solution going to help with the OP's cross platform question?? – alex tingle Oct 01 '09 at 18:50
  • I said *if* project configurations are supported. – Jon Seigel Oct 01 '09 at 18:53
  • 3
    @Jon: Hmm. They are NOT supported in any kind of cross-platform environment *by definition*. Unless it is MS's definition of cross-platform - works on newer flavors of Windows. – EFraim Oct 01 '09 at 19:00
  • 1
    @EFraim: Yes, you can TARGET 32- or 64-bit using VS, but that is not what I am talking about. Generic project configurations, and the names I assign them, have absolutely nothing to do with platform. If project configurations are VS-specific, then that's a shame because they're very handy. – Jon Seigel Oct 01 '09 at 19:23
  • 1
    I think this is the right answer. It's more reliable than trying to autodetect things. All IDEs I've ever seen support this feature in some form, and I bet the ones I've never seen support it too. If you use make, or jam, you can set the variables from the command line when invoked, in the usual fashion. –  Oct 01 '09 at 23:27
1

I'd place 32-bit and 64-bit sources in different files and then select appropriate source files using the build system.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
big-z
  • 6,812
  • 5
  • 20
  • 19
  • 2
    This would be similar to having the build system giving you a flag such as `-DBUILD_64BIT`. Often, certain things are very similar to both 32 and 64 bit so having it in the same file can be quite practical. – Alexis Wilke Oct 21 '14 at 23:36
  • Maintaining twin source files is error prone. IMO even a huge #if bit64 .. all code, for 64bit #else .. all code, for 32bit #endif is better than that. (#if's line-by-line is ideal in my view) – brewmanz May 03 '18 at 05:57
0

I'm adding this answer as a use case and complete example for the runtime-check described in another answer.

This is the approach I've been taking for conveying to the end-user whether the program was compiled as 64-bit or 32-bit (or other, for that matter):

version.h

#ifndef MY_VERSION
#define MY_VERSION

#include <string>

const std::string version = "0.09";
const std::string arch = (std::to_string(sizeof(void*) * 8) + "-bit");

#endif

test.cc

#include <iostream>
#include "version.h"

int main()
{
    std::cerr << "My App v" << version << " [" << arch << "]" << std::endl;
}

Compile and Test

g++ -g test.cc
./a.out
My App v0.09 [64-bit]
Parker
  • 7,244
  • 12
  • 70
  • 92