Determinación de 32 vs 64 bit en C ++

136

Estoy buscando una manera de determinar de manera confiable si el código C ++ se está compilando en 32 contra 64 bits. Se nos ocurrió lo que creemos que es una solución razonable utilizando macros, pero tenía curiosidad por saber si la gente podría pensar en casos en los que esto podría fallar o si hay una mejor manera de hacerlo. Tenga en cuenta que estamos tratando de hacer esto en un entorno multiplataforma y compilador múltiple.

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

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

Gracias.

Joe Corkery
fuente
8
Si realmente le importa cuál es el tamaño de palabra de su arquitectura, no pase por alto la posibilidad de que no sea ni de 32 ni de 64 bits. Hay arquitecturas de 16 y 128 bits, ya sabes.
alex tingle
¿Cuál es la diferencia entre la operación de 64 bits y la de 32 bits?
peterchen 01 de
2
Realmente no deberías condicionalizar esto en el ancho de palabra de la plataforma de destino. En su lugar, use el tamaño de los tipos de datos relevantes directamente para determinar qué hacer. stdint.hpuede ser tu amigo, o puede que necesites desarrollar algunos tipos de letra apropiados.
Phil Miller el
Esta prueba no parece funcionar en Visual Studio 2008 SP1. Se atasca en "IS64BIT" tanto para 32 bits como para 64 bits.
Contango

Respuestas:

99

Desafortunadamente, no existe una macro multiplataforma que defina 32/64 bits en los principales compiladores. He encontrado que la forma más efectiva de hacer esto es la siguiente.

Primero elijo mi propia representación. Prefiero ENVIRONMENT64 / ENVIRONMENT32. Luego descubro lo que utilizan todos los compiladores principales para determinar si es un entorno de 64 bits o no y lo uso para establecer mis 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

Otra ruta más fácil es simplemente establecer estas variables desde la línea de comandos del compilador.

JaredPar
fuente
3
bueno, existen otros compiladores además de GCC y VS. Por ejemplo, me vienen a la mente QNX y GHS (aunque sospecho que QNX tiene un tiempo de construcción similar al de GCC). También olvidó las arquitecturas MIPS64 e IA64 en su cheque GCC
Rom
14
@Rom, definitivamente más de 2 compiladores y arquitecturas. Esto solo pretende ser una muestra de cómo abordar este problema, no una solución completa.
JaredPar 01 de
2
Yo digo "generalmente". "Idealmente" es probablemente más realista.
Steve Jessop
77
Creo que deberías usar "#if defined ( WIN32 ) || defined (_WIN64)" etc
KindDragon
3
#if _WIN32 || _WIN64... #elif __GNUC__... #else # error "Missing feature-test macro for 32/64-bit on this compiler."?
Davislor
100
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
fuente
2
¿Qué sucede si size_t no es ni 4 ni 8?
Jesper
16
@Jesper, entonces obtendrás un error de enlace en la muestra anterior. O podría implementar DoMyOperation para ese caso
Kirill V. Lyadvinsky
1
Uso hábil de plantillas y felicitaciones para probar lo que importa (el tamaño de algún tipo en particular) en lugar de un correlato.
Phil Miller el
2
Cuidado con el uso de size_t para esto. Por ejemplo, puede tener problemas en los que no corresponde al tamaño del puntero (por ejemplo, en plataformas con más de un tamaño de puntero).
Logan Capaldo
8
Standard dice que el tamaño de size_tes lo suficientemente grande como para contener el tamaño de cualquier objeto asignado en el sistema. Por lo general, es lo que quieres saber al compilar condicionalmente. Si no es lo que desea, puede usar este fragmento con otro tipo en lugar de size_t. Por ejemplo, podría ser void*.
Kirill V. Lyadvinsky
44

Desafortunadamente, en un entorno de plataforma cruzada, compilador cruzado, no existe un método confiable único para hacer esto puramente en tiempo de compilación.

  • Tanto _WIN32 y _WIN64 pueden a veces tanto indefinidos, si la configuración del proyecto son defectuosos o dañados (particularmente en Visual Studio 2008 SP1).
  • Un proyecto etiquetado como "Win32" podría establecerse en 64 bits, debido a un error de configuración del proyecto.
  • En Visual Studio 2008 SP1, a veces el intellisense no atenúa las partes correctas del código, de acuerdo con el #define actual. Esto hace que sea difícil ver exactamente qué #define se está utilizando en tiempo de compilación.

Por lo tanto, el único método confiable es combinar 3 verificaciones simples :

  • 1) Configuración del tiempo de compilación , y;
  • 2) Verificación de tiempo de ejecución , y;
  • 3) Comprobación robusta del tiempo de compilación .

Comprobación simple 1/3: configuración del tiempo de compilación

Elija cualquier método para establecer la variable #define requerida. Sugiero el método de @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

Verificación simple 2/3: Verificación de tiempo de ejecución

En main (), verifique dos veces si sizeof () tiene sentido:

#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

Comprobación simple 3/3: comprobación robusta del tiempo de compilación

La regla general es "cada #definir debe terminar en un #else que genera un 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

Actualizar 2017-01-17

Comentario de @AI.G:

4 años después (no sé si era posible antes) puede convertir la verificación en tiempo de ejecución en una en tiempo de compilación utilizando la aserción estática: static_assert (sizeof (void *) == 4) ;. Ahora todo está hecho en tiempo de compilación :)

Apéndice A

Incidentalmente, las reglas anteriores se pueden adaptar para hacer que toda su base de código sea más confiable:

  • Cada instrucción if () termina en un "else" que genera una advertencia o error.
  • Cada instrucción switch () termina en un "valor predeterminado:" que genera una advertencia o error.

La razón por la que esto funciona bien es que te obliga a pensar en cada caso por adelantado, y no confiar en la lógica (a veces defectuosa) en la parte "else" para ejecutar el código correcto.

Utilicé esta técnica (entre muchas otras) para escribir un proyecto de 30,000 líneas que funcionó perfectamente desde el día en que se implementó por primera vez en producción (eso fue hace 12 meses).

Aplazamiento de pago
fuente
sizeof(void*)¿Se resuelve en tiempo de compilación o en tiempo de ejecución? si es en tiempo de compilación, entonces en tiempo de ejecución la verificación siempre será if(8!=8){...}.
Ameen
@ameen Se resuelve en tiempo de ejecución. El objetivo de esta verificación es asegurar que el programa salga con un error apropiado si el bitness no es el esperado. Esto significa que el desarrollador puede corregir este error inmediatamente, en lugar de tratar de diagnosticar errores sutiles que aparecen más adelante.
Contango
3
4 años más tarde (no sé si era posible antes) se puede convertir el cheque en tiempo de ejecución a tiempo de compilación usando una aserción estática: static_assert(sizeof(void*) == 4);. Ahora todo está hecho en tiempo de compilación :)
Al.G.
1
static_assert(sizeof(void*) * CHAR_BIT == 32)es más expresivo y técnicamente correcto (aunque no conozco ninguna arquitectura donde los bytes tengan una cantidad diferente de bits que 8)
Xeverous
1
Vea también mi respuesta a continuación que combina esta excelente respuesta con " Mejores macros, mejores indicadores " de Fluent C ++.
metal
30

Debería poder usar las macros definidas en stdint.h. En particular, INTPTR_MAXes exactamente el valor que necesita.

#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

Algunas versiones (¿todas?) Del compilador de Microsoft no vienen con stdint.h . No estoy seguro de por qué, ya que es un archivo estándar. Aquí hay una versión que puede usar:http://msinttypes.googlecode.com/svn/trunk/stdint.h

alex tingle
fuente
44
¿Por qué no stdint.h para Microsoft? Debido a que se introdujo con el estándar C99, y Microsoft parece tener una aversión activa a implementar incluso las cosas más fáciles de C99. Incluso las cosas fáciles de la biblioteca que no requieren cambio de compilador. Incluso lo que ya se está haciendo al compilar para C ++ (como las declaraciones después de las declaraciones). Sé que necesita pruebas, etc., pero también sé que MS obtiene (o una vez obtuvo) una buena parte de su biblioteca de Dinkumware / Plauger, y Dinkumware ha tenido el material de la biblioteca C99 durante años.
Michael Burr el
2
VC ++ 2010 (beta 1, de todos modos) tiene <stdint.h>y <cstdint>. En cuanto al estado actual de las cosas, la biblioteca VC ++ se origina en Dinkumware (todavía lo hace, TR1 también se tomó de allí), pero por lo que recuerdo leer en VCBlog, se somete a una refactorización bastante significativa para compilar limpiamente /clr, trabajar con todos los MSVC tipos no estándar como __int64, etc., por lo que no es tan simple como tomarlo y ponerlo en la próxima versión del compilador.
Pavel Minaev
2
Esto me llevó a la respuesta correcta, pero creo que debería compararlo con UINT64_MAX, no con INT64_MAX. Usé SIZE_MAX == UINT64_MAX - probablemente igual
Arno Duvenhage
15

Eso no funcionará en Windows para empezar. Los largos y las entradas son 32 bits, ya sea que esté compilando para ventanas de 32 bits o 64 bits. Creo que verificar si el tamaño de un puntero es de 8 bytes es probablemente una ruta más confiable.

Mattnewport
fuente
2
Desafortunadamente, sizeof está prohibido en la directiva #if (si lo piensa, el preprocesador no tiene forma de saberlo)
EFraim
Sí, por eso lo dejé sugiriendo verificar el tamaño de un puntero en lugar de usar sizeof - No puedo pensar en una forma portátil de hacerlo desde la parte superior de mi cabeza ...
Mattnewport
3
Cuestión no (todavía) decir que tiene que ser hecho en el momento pre-procesador. Muchos / la mayoría de los compiladores con la optimización activada harán un trabajo decente al eliminar el código muerto, incluso si lo "deja hasta el tiempo de ejecución" con una prueba como sizeof(void*) == 8 ? Do64Bit() : Do32Bit();. Eso aún podría dejar una función no utilizada en el binario, pero la expresión probablemente se compila solo con una llamada a la función "correcta".
Steve Jessop
1
@onebyone que resuelve el problema de las llamadas a funciones, pero ¿qué pasa si quiero declarar una variable de un tipo diferente en función de la plataforma? Eso tendría que hacerse en el preprocesador a menos que desee declarar varias variables y usarlas en función de una instrucción if ( que también se optimizaría si no se usan, pero no sería muy agradable en el código)
Falaina
1
Entonces tienes razón, una expresión constante en condicional no es buena. El enfoque de Kirill puede hacer lo que quiera, sin embargo:template<int> struct Thing; template<> struct Thing<4> { typedef uint32_t type; }; template<> struct Thing<8> { typedef uint64_t type; }; typedef Thing<sizeof(void*)>::type thingtype;
Steve Jessop
9

Podrías hacer esto:

#if __WORDSIZE == 64
char *size = "64bits";
#else
char *size = "32bits";
#endif
Anoop
fuente
1
En muchos entornos de programación para lenguajes derivados de C y C en máquinas de 64 bits, las variables "int" todavía tienen 32 bits de ancho, pero los enteros y punteros largos tienen 64 bits de ancho. Estos se describen como que tienen un modelo de datos LP64. unix.org/version2/whatsnew/lp64_wp.html
Hermes
6
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
emj8321
fuente
77
Este código no es correcto En 64 bits, tanto _WIN32 como _WIN64 están definidos. Si lo cambia (primero verifique _WIN64) funciona, por supuesto.
BertR
4

"Compilado en 64 bits" no está bien definido en C ++.

C ++ establece solo límites inferiores para tamaños como int, long y void *. No hay garantía de que int sea de 64 bits, incluso cuando se compila para una plataforma de 64 bits. El modelo permite, por ejemplo, 23 bit intsysizeof(int *) != sizeof(char *)

Existen diferentes modelos de programación para plataformas de 64 bits.

Su mejor apuesta es una prueba específica de la plataforma. Su segunda mejor decisión portátil debe ser más específica en lo que es 64 bits.

Peterchen
fuente
3

Su enfoque no estaba muy lejos, pero solo está verificando si son del mismo tamaño longy si intson del mismo tamaño. Teóricamente, ambos podrían ser de 64 bits, en cuyo caso su verificación fallaría, suponiendo que ambos sean de 32 bits. Aquí hay una verificación que realmente verifica el tamaño de los tipos en sí, no su tamaño relativo:

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

En principio, puede hacer esto para cualquier tipo para el que tenga una macro definida por el sistema con el valor máximo.

Tenga en cuenta que el estándar requiere long longal menos 64 bits, incluso en sistemas de 32 bits.

cmaster - restablecer monica
fuente
Una cosa a tener en cuenta, para que se definan UINT_MAX y ULONG_MAX, probablemente desee tener #include <limits.h>algún lugar antes de sus #ifpruebas.
Alexis Wilke
3

La gente ya sugirió métodos que intentarán determinar si el programa se está compilando 32-bito64-bit .

Y quiero agregar que puedes usar la función c ++ 11 static_assert para asegurarse de que la arquitectura es lo que usted piensa que es ("para relajarse").

Entonces, en el lugar donde define las 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
fuente
static_assert(sizeof(void*) * CHAR_BIT == 32)es más expresivo y técnicamente correcto (aunque no conozco ninguna arquitectura donde los bytes tengan una cantidad diferente de bits que 8)
Xeverous
2

El siguiente código funciona bien para la mayoría de los entornos actuales:

  #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
fuente
3
Tenga en cuenta que _WIN64requiere que ya haya incluido <windows.h>. Con Visual C ++, es mejor utilizar el built-in define compilador: _M_IX86, _M_X64, _M_ARM, _M_ARM64, etc.
Chuck Walbourn
Para PowerPC, creo que usted necesita para comprobar si hay __ppc64__, __powerpc64__y _ARCH_PPC64. Eso también atrapa a AIX y otras plataformas.
jww
1

Si puede usar configuraciones de proyecto en todos sus entornos, eso facilitaría la definición de un símbolo de 64 y 32 bits. Entonces tendrías configuraciones de proyecto como esta:

Depuración de
32 bits Versión de 32 bits Versión de
64 bits Depuración de
64 bits

EDITAR: Estas son configuraciones genéricas, no configuraciones dirigidas. Llámalos como quieras.

Si no puedes hacer eso, me gusta la idea de Jared.

Jon Seigel
fuente
O combine los dos: detecte automáticamente la configuración en los compiladores que conoce, pero recurra a mirar un #define especificado en el proyecto / línea de comandos / lo que sea en compiladores no reconocidos.
Steve Jessop
44
¿Cómo va a ayudar su solución específica de VisualStudio con la pregunta multiplataforma del OP?
alex tingle
3
@ Jon: Hmm. NO son compatibles en ningún tipo de entorno multiplataforma por definición . A menos que sea la definición de multiplataforma de MS, funciona en versiones más nuevas de Windows.
EFraim el
1
@EFraim: Sí, puedes OBTENER OBJETIVOS de 32 o 64 bits usando VS, pero eso no es de lo que estoy hablando. Las configuraciones genéricas de proyectos y los nombres que les asigno no tienen absolutamente nada que ver con la plataforma. Si las configuraciones del proyecto son específicas de VS, entonces es una pena porque son muy útiles.
Jon Seigel
1
Creo que esta es la respuesta correcta. Es más confiable que tratar de autodetectar cosas. Todos los IDE que he visto admiten esta característica de alguna forma, y ​​apuesto a que los que nunca he visto también lo admiten. Si utiliza make o jam, puede establecer las variables desde la línea de comando cuando se invoca, de la manera habitual.
1

Colocaría fuentes de 32 bits y 64 bits en diferentes archivos y luego seleccionaría los archivos fuente apropiados usando el sistema de compilación.

big-z
fuente
2
Esto sería similar a tener el sistema de compilación dándole una bandera como -DBUILD_64BIT. A menudo, ciertas cosas son muy similares a 32 y 64 bits, por lo que tenerlo en el mismo archivo puede ser bastante práctico.
Alexis Wilke
Mantener archivos fuente gemelos es propenso a errores. OMI, incluso un gran #if bit64 ... todo el código, para 64 bits #else ... todo el código, para 32 bits #endif es mejor que eso. (# if's línea por línea es ideal en mi opinión)
brewmanz
1

Tomando prestado de la excelente respuesta anterior de Contango y combinándolo con " Mejores macros, mejores banderas " de Fluent C ++, puede hacer:

// 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

Entonces puedes usarlo como:

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

O usando la macro adicional que agregué:

MYPROJ_IF_64_BIT_ELSE( DoMy64BitOperation(), DoMy32BitOperation() );
metal
fuente
0

Estoy agregando esta respuesta como un caso de uso y un ejemplo completo para la verificación de tiempo de ejecución descrita en otra respuesta .

Este es el enfoque que he estado adoptando para transmitir al usuario final si el programa se compiló como 64 bits o 32 bits (u otro, para el caso):

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;
}

Compilar y probar

g++ -g test.cc
./a.out
My App v0.09 [64-bit]
vallismortis
fuente