I am using gcc/g++. The below code compiles fine with gcc -S test.c, however with g++ -S test.cpp I get error: requested alignment is not an integer constant. If I look at the preprocessor output for both it looks identical. So my question is why isn't ALIGN_BYTES being evaluated to the constant 64 by the preprocessor in the C++ case? (If I replace ALIGN_BYTES with the constant 64 it works fine)
/* test.c, test.cpp */
#define BITS 512
#define ALIGN_BYTES (BITS / 8)
#define ALIGN __attribute__ ((aligned(ALIGN_BYTES)))
typedef char* ALIGN char_PT;
It is not a macro expansion issue. The macro is not expanded to a constant in either C or C++, here. The preprocessor does not do arithmetic, so it simply generates the expression 512 / 8 which isn't a constant, but which the compiler should definitely be able to reduce to one.
The preprocessor generates the same code for C and C++ here, but GCC (for reasons I do not understand) treats the __attribute__ extension differently in the two languages. I really have no idea why, there likely are good reasons, but someone else will have to explain that.
If you compile C, gcc is happy with aligned((512 / 8)), but if you compile C++ with g++ it will complain that 512 / 8 is not a constant. It is right, I guess, but really also wrong.
There are other cases where it is the opposite, where g++ is happy with a non-constant, but gcc is not. If you declare a static const int for example, you can use it in __attribute__((aligned(...)) in C++ but not in C. Again, I cannot explain why. It's a compiler extension and GCC can do whatever. And for some reason, it will treat the two languages differently here.
/* g++ will complain about this one; gcc will not */
typedef char *__attribute__((aligned((512 / 8)))) char_PT;
/* gcc will complain about this one; g++ will not */
static const int A = 16;
typedef char *__attribute__((aligned(A))) char_PT2;
I suppose, though, that since we know one version that works with C and another that works with C++, we could do this:
#define BITS 512
#ifdef __cplusplus
static const unsigned int ALIGN_BYTES = BITS / 8;
#define ALIGN __attribute__((aligned(ALIGN_BYTES)))
#else /* C */
#define ALIGN_BYTES (BITS / 8)
#define ALIGN __attribute__((aligned(ALIGN_BYTES)))
#endif
typedef char *ALIGN char_PT;
As #Deduplicator suggested, __attribute__ is a gcc extension. Using the below fixed the problem.
#ifdef __cplusplus
#define ALIGN alignas(ALIGN_BYTES)
#else
#define ALIGN __attribute__ ((aligned(ALIGN_BYTES)))
#endif
Related
I'm trying to write a C++ library, which is going to target an ARM Linux system, but leaving C-compatible "bindings" and structs for using with CFFI on other languages.
The library deals with a serial stream that I don't control, and that I want to de-serialize.
I have the following (simplified) struct on stream.h:
#pragma once
#include "cpp_compat.h"
#include <stdbool.h>
#include <stdint.h>
#define BB_FRAME_IQ_SAMPLES_COUNT 512
#define BB_FRAME_MAGIC 0xAA5555AA
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct __attribute__((__packed__))
{
/** Every frame starts with BB_FRAME_MAGIC. */
uint32_t magic;
/** Baseband IQ samples. */
fcomplex_t bb_iq_samples[BB_FRAME_IQ_SAMPLES_COUNT];
uint8_t someparams[9]; // simplification
uint32_t reserved_n[6];
uint32_t crc;
} bb_frame_t;
#ifdef __cplusplus
}
#endif
fcomplex_t is where the C/C++ compatibility happens, thanks to cpp_compat.h:
#pragma once
#ifdef __cplusplus
#include <complex>
using fcomplex_t = std::complex<float>;
#else
#include <complex.h>
typedef _Complex float fcomplex_t;
#endif
However, I'm having this warning when building the code in C++:
[build] stream.h:41:37: warning: ignoring packed attribute because of unpacked non-POD field ‘fcomplex_t <unnamed struct>::bb_iq_samples [512]’
[build] 41 | fcomplex_t bb_iq_samples[BB_FRAME_IQ_SAMPLES_COUNT];
[build] | ^~~~~~~~~~~~~
And this scares me because I really need the packed attribute to work to correctly de-serialize the frames from the serial stream.
The compiler is arm-linux-gnueabihf-g++ (GCC) 12.2.1 20221203 [releases/gcc-12 revision c03cb4b762aceeba95da918b042583af0d9f6030]. I got it from a buildroot using the Linaro toolchain.
So the question is, how can I make this work without errors?
P.S.: It's worth noting that the C++ standard guarantees that std::complex<float> and _Complex float are memory-compatible.
EDIT 1: In the meanwhile, I found out that x86's GCC also complains about the issue but Clang (tested with version 16) does not. Seems to be something GCC-specific.
Raised the problem on GCC.
It's apparently an intended behaviour of the compiler.
More details in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108342
Is there a way to show the memory "pack" size with GCC ?
In Microsoft Visual C++, I am using:
#pragma pack(show)
which displays the value in a warning message; see Microsoft's documentation.
What is the equivalent with GCC?
Since I can't see such functionality listed in the pertinent documentation, I'm going to conclude that GCC cannot do this.
I use a static assertion whenever I pack a structure and want to see its size.
/*
The static_assert macro will generate an error at compile-time, if the predicate is false
but will only work for predicates that are resolvable at compile-time!
E.g.: to assert the size of a data structure, static_assert(sizeof(struct_t) == 10)
*/
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
/* token pasting madness: */
#define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,at_line_##L) /* add line-number to error message for better warnings, especially GCC will tell the name of the variable as well */
#define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X, L) /* expand line-number */
#define static_assert(X) COMPILE_TIME_ASSERT2(X, __LINE__) /* call with line-number macro */
#define PACKED __attribute__ ((gcc_struct, __packed__))
typedef struct {
uint8_t bytes[3];
uint32_t looong;
} PACKED struct_t;
static_assert(sizeof(struct_t) == 7);
This will give you a compile time warning whenever the static assertion fails.
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.
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.
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;
}
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).
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
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.
You could do this:
#if __WORDSIZE == 64
char *size = "64bits";
#else
char *size = "32bits";
#endif
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
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
"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.
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.
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
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() );
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.
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.
I'd place 32-bit and 64-bit sources in different files and then select appropriate source files using the build system.
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]
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.
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.
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;
}
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).
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
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.
You could do this:
#if __WORDSIZE == 64
char *size = "64bits";
#else
char *size = "32bits";
#endif
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
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
"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.
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.
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
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() );
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.
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.
I'd place 32-bit and 64-bit sources in different files and then select appropriate source files using the build system.
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]
For example, let's say I have a function that will swap bytes in a 32 bit value for you:
uint32_t byte_swap(uint32_t in);
Well it seems silly to push that 32-bit value onto the stack and pop it off again, especially if we're going to be calling this function a lot, so let's pass it in through ECX:
#if __FASTCALL_SUPPORTED_ /* Whatever this may be */
#define FASTCALL __attribute__((fastcall))
#else
#define FASTCALL
#endif
uint32_t FASTCALL byte_swap(uint32_t in);
Now my question is, is it safe to compile that function into a shared library for distribution? If the user uses a different compiler to compile their program and links against this, will the function still be called properly?
__attribute__((fastcall)) is a gcc extension; as such, it may not be usable if the caller is not using gcc as well. Moreover, in the sample you gave, if __FASTCALL_SUPPORTED_ is not defined, you'll end up with a call with the wrong calling convention - bad idea.
One way to deal with this may be using a fallback wrapper. In your .c file:
#include "foo.h"
uint32_t FASTCALL byte_swap(uint32_t in) {
/* code ... */
}
uint32_t byte_swap__slowcall(uint32_t in) {
return byte_swap(in);
}
And in your .h file:
#if __FASTCALL_SUPPORTED_ /* Whatever this may be */
#define FASTCALL __attribute__((fastcall))
#else
#define FASTCALL
#define byte_swap byte_swap__slowcall
#endif
uint32_t FASTCALL byte_swap(uint32_t in);
Also, note that on Linux, a fast byteswap implementation is available in <byteswap.h> as bswap_32. On x86 machines, it will compile down to inline assembler, and a single instruction on high enough -march= settings.