Determinazione di 32 vs 64 bit in C ++


136

Sto cercando un modo per determinare in modo affidabile se il codice C ++ viene compilato in 32 vs 64 bit. Abbiamo trovato quella che pensiamo sia una soluzione ragionevole usando le macro, ma siamo curiosi di sapere se le persone potrebbero pensare a casi in cui ciò potrebbe fallire o se esiste un modo migliore per farlo. Si noti che stiamo provando a farlo in un ambiente con più compilatori multipiattaforma.

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

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

Grazie.


8
Se ti interessa davvero quale sia la dimensione della parola della tua architettura, allora non trascurare la possibilità che non sia né 32 né 64 bit. Ci sono architetture a 16 e 128 bit là fuori, sai.
alex tingle,

Qual è la differenza tra l'operazione a 64 bit e quella a 32 bit?
Peter

2
Non dovresti davvero condizionarlo alla larghezza delle parole della piattaforma di destinazione. Utilizzare invece la dimensione dei tipi di dati rilevanti direttamente per determinare cosa fare. stdint.hpotrebbe essere tuo amico o potresti aver bisogno di sviluppare alcuni typedef appropriati per conto tuo.
Phil Miller,

Questo test non sembra funzionare su Visual Studio 2008 SP1. Si blocca su "IS64BIT" sia a 32-bit che a 64-bit.
Contango,

Risposte:


99

Sfortunatamente non esiste una macro multipiattaforma che definisce 32/64 bit tra i principali compilatori. Ho trovato il modo più efficace per farlo è il seguente.

Per prima cosa scelgo la mia rappresentazione. Preferisco ENVIRONMENT64 / ENVIRONMENT32. Poi scopro cosa usano tutti i principali compilatori per determinare se si tratta di un ambiente a 64 bit o meno e lo uso per impostare le mie variabili.

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

Un altro percorso più semplice è semplicemente impostare queste variabili dalla riga di comando del compilatore.


3
bene, esistono altri compilatori oltre a GCC e VS. Ad esempio, vengono in mente QNX e GHS (anche se sospetto che QNX abbia definizioni di tempo di produzione simili a GCC). Inoltre hai dimenticato le architetture MIPS64 e IA64 nel tuo controllo GCC
Rom

14
@ Rom, sicuramente più di 2 compilatori e architetture. Questo vuole essere solo un esempio di come affrontare questo problema, non una soluzione completa.
JaredPar,

2
Dico "di solito". "Idealmente" è probabilmente più realistico.
Steve Jessop,

7
Penso che dovresti usare "#if defined ( WIN32 ) || definito (_WIN64)" ecc.
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;
}

2
Cosa succede se size_t non è né 4 né 8?
Jesper,

16
@Jesper, allora otterrai un errore di collegamento nell'esempio sopra. Oppure potresti implementare DoMyOperation per quel caso
Kirill V. Lyadvinsky,

1
Uso semplice di modelli e complimenti per testare ciò che conta (la dimensione di un determinato tipo) piuttosto che un correlato.
Phil Miller,

2
Attento con l'utilizzo di size_t per questo. Ad esempio, potresti riscontrare problemi in cui non corrisponde alla dimensione del puntatore (ad es. Su piattaforme con più di una dimensione del puntatore).
Logan Capaldo,

8
Lo standard afferma che la dimensione di size_tè abbastanza grande da contenere la dimensione di qualsiasi oggetto allocato nel sistema. Di solito è quello che vuoi sapere durante la compilazione condizionale. Se non è quello che desideri, puoi utilizzare questo frammento con un altro tipo anziché size_t. Ad esempio, potrebbe essere void*.
Kirill V. Lyadvinsky,

44

Sfortunatamente, in un ambiente cross-platform e cross-compilatore, non esiste un unico metodo affidabile per farlo puramente in fase di compilazione.

  • A volte sia _WIN32 che _WIN64 possono essere entrambi indefiniti, se le impostazioni del progetto sono errate o danneggiate (in particolare su Visual Studio 2008 SP1).
  • Un progetto con l'etichetta "Win32" potrebbe essere impostato su 64 bit, a causa di un errore di configurazione del progetto.
  • Su Visual Studio 2008 SP1, a volte l'intellisense non oscura le parti corrette del codice, secondo l'attuale #define. Questo rende difficile vedere esattamente quale #define viene usato al momento della compilazione.

Pertanto, l' unico metodo affidabile è combinare 3 semplici controlli :

  • 1) Impostazione del tempo di compilazione e;
  • 2) controllo di runtime e;
  • 3) Controllo del tempo di compilazione robusto .

Controllo semplice 1/3: impostazione del tempo di compilazione

Scegli un metodo per impostare la variabile #define richiesta. Suggerisco il metodo da @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

Controllo semplice 2/3: controllo runtime

In main (), controlla due volte se sizeof () ha senso:

#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

Controllo semplice 3/3: controllo del tempo di compilazione robusto

La regola generale è "ogni #define deve finire con un #else che genera un errore".

#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

Aggiornamento 2017-01-17

Commento di @AI.G:

4 anni dopo (non so se fosse possibile prima) è possibile convertire il controllo di run-time in tempo di compilazione usando assert statico: static_assert (sizeof (void *) == 4) ;. Ora è tutto fatto in fase di compilazione :)

Appendice A

Per inciso, le regole sopra possono essere adattate per rendere più affidabile l'intera base di codice:

  • Ogni istruzione if () termina con un "else" che genera un avviso o un errore.
  • Ogni istruzione switch () termina con un "valore predefinito:" che genera un avviso o un errore.

Il motivo per cui funziona bene è che ti costringe a pensare in anticipo a ogni singolo caso e non fare affidamento sulla logica (a volte imperfetta) nella parte "else" per eseguire il codice corretto.

Ho usato questa tecnica (tra le altre cose) per scrivere un progetto di 30.000 linee che ha funzionato perfettamente dal giorno in cui è stato implementato per la prima volta in produzione (12 mesi fa).


sizeof(void*)viene risolto in fase di compilazione o runtime? se è in fase di compilazione, quindi in fase di esecuzione il controllo sarà sempre if(8!=8){...}.
Ameen

@ameen È stato risolto in fase di esecuzione. Lo scopo di questo controllo è garantire che il programma esca con un errore appropriato se il testimone non è quello che ci si aspetta. Ciò significa che lo sviluppatore può correggere immediatamente questo errore, piuttosto che provare a diagnosticare bug sottili che verranno visualizzati in seguito.
Contango,

3
4 anni più tardi (non so se era possibile prima) è possibile convertire il controllo in fase di esecuzione per compilare in tempo una utilizzando affermare statica: static_assert(sizeof(void*) == 4);. Ora è tutto fatto in fase di compilazione :)
Al.G.

1
static_assert(sizeof(void*) * CHAR_BIT == 32)è più espressivo e tecnicamente corretto (anche se non conosco alcuna architettura in cui i byte abbiano una quantità di bit diversa da 8)
Xeverous

1
Vedi anche la mia risposta di seguito che combina questa eccellente risposta con " Better Macros, Better Flags " di Fluent C ++.
metallo

30

Dovresti essere in grado di utilizzare le macro definite in stdint.h. In particolare INTPTR_MAXè esattamente il valore che ti serve.

#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

Alcune (tutte?) Versioni del compilatore di Microsoft non vengono fornite stdint.h. Non so perché, dato che è un file standard. Ecco una versione che puoi usare:http://msinttypes.googlecode.com/svn/trunk/stdint.h


4
Perché no stdint.h per Microsoft? Perché è stato introdotto con lo standard C99 e Microsoft sembra avere un'avversione attiva per l'implementazione anche delle cose più semplici del C99. Anche le semplici cose della libreria che non richiedono modifiche al compilatore. Anche le cose che sono già state fatte durante la compilazione per C ++ (come dichiarazioni dopo dichiarazioni). So che ha bisogno di essere testato, ecc., Ma so anche che MS ottiene (o una volta ottenuto) una buona parte della sua libreria da Dinkumware / Plauger, e Dinkumware ha avuto le cose della libreria C99 in giro per anni.
Michael Burr,

2
VC ++ 2010 (beta 1, comunque) ha <stdint.h>e <cstdint>. Per quanto riguarda lo stato attuale delle cose - la libreria VC ++ proviene da Dinkumware (lo fa ancora - TR1 è stata presa anche da lì), ma da ciò che ricordo di aver letto su VCBlog, subisce un refactoring abbastanza significativo da compilare in modo pulito /clr, funziona con tutto MSVC tipi non standard come __int64e così via - ecco perché non è così semplice come prenderlo e metterlo nella prossima versione del compilatore.
Pavel Minaev,

2
Questo mi ha portato alla risposta corretta, ma penso che dovresti confrontarti con UINT64_MAX e non INT64_MAX. Ho usato SIZE_MAX == UINT64_MAX - probabilmente lo stesso
Arno Duvenhage

15

Per prima cosa non funzionerà su Windows. I long e gli ints sono entrambi 32 bit sia che si stiano compilando per finestre a 32 bit o 64 bit. Penso che verificare se la dimensione di un puntatore è di 8 byte sia probabilmente un percorso più affidabile.


2
Sfortunatamente sizeof è proibito nella direttiva #if (se ci pensate, il preprocessore non ha modo di dirlo)
EFraim

Sì, è per questo che l'ho lasciato a suggerire di controllare la dimensione di un puntatore piuttosto che usare sizeof - Non riesco a pensare a un modo portatile per farlo dalla parte superiore della mia testa ...
Mattnewport

3
La domanda non (ancora) dice che deve essere fatto al momento del pre-processore. Molti / molti compilatori con l'ottimizzazione attiva faranno un lavoro decente nell'eliminazione del codice morto, anche se "lo lasci fino al runtime" con un test simile sizeof(void*) == 8 ? Do64Bit() : Do32Bit();. Ciò potrebbe comunque lasciare una funzione inutilizzata nel binario, ma l'espressione è probabilmente compilata solo per una chiamata alla funzione "giusta".
Steve Jessop,

1
@onebyone che risolve il problema delle chiamate di funzione, ma cosa succede se voglio dichiarare una variabile di tipo diverso in base alla piattaforma, ciò dovrebbe essere fatto sul preprocessore a meno che tu non voglia dichiarare più variabili e usarle in base a un'istruzione if ( che sarebbe anche ottimizzato se non utilizzato, ma non sarebbe molto piacevole nel codice)
Falaina,

1
Allora hai ragione, un'espressione costante in un condizionale non va bene. L'approccio di Kirill può fare quello che vuoi, però: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

Potresti farlo:

#if __WORDSIZE == 64
char *size = "64bits";
#else
char *size = "32bits";
#endif

1
In molti ambienti di programmazione per linguaggi derivati ​​da C e C su macchine a 64 bit, le variabili "int" sono ancora larghe 32 bit, ma i numeri interi e i puntatori lunghi sono larghi 64 bit. Questi sono descritti come aventi un modello di dati 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

7
Questo codice non è corretto Su 64 bit sono definiti sia _WIN32 che _WIN64. Se lo giri (controlla prima per _WIN64) funziona ovviamente.
BertR

4

"Compilato in 64 bit" non è ben definito in C ++.

C ++ imposta solo limiti inferiori per dimensioni come int, long e void *. Non vi è alcuna garanzia che int sia 64 bit anche se compilato per una piattaforma a 64 bit. Il modello consente ad esempio a 23 bit ints esizeof(int *) != sizeof(char *)

Esistono diversi modelli di programmazione per piattaforme a 64 bit.

La tua scommessa migliore è un test specifico per piattaforma. La tua seconda migliore decisione portatile deve essere più specifica in ciò che è 64 bit.


3

Il tuo approccio non era troppo lontano, ma stai solo verificando se longe inthanno le stesse dimensioni. Teoricamente, potrebbero essere entrambi 64 bit, nel qual caso il tuo controllo fallirebbe, assumendo entrambi 32 bit. Ecco un controllo che controlla effettivamente la dimensione dei tipi stessi, non la loro dimensione relativa:

#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 linea di principio, puoi farlo per qualsiasi tipo per cui hai una macro definita dal sistema con il valore massimo.

Si noti che lo standard richiede long longdi essere almeno 64 bit anche su sistemi a 32 bit.


Una cosa da notare, per definire UINT_MAX e ULONG_MAX, probabilmente vorrai avere un #include <limits.h>posto prima dei tuoi #iftest.
Alexis Wilke,

3

Le persone hanno già suggerito metodi che tenteranno di determinare se il programma viene compilato in 32-bito 64-bit.

E voglio aggiungere che puoi usare la funzione c ++ 11 static_assertper assicurarti che l'architettura sia quella che pensi ("per rilassarti").

Quindi nel luogo in cui si definiscono le macro:

#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

static_assert(sizeof(void*) * CHAR_BIT == 32)è più espressivo e tecnicamente corretto (anche se non conosco alcuna architettura in cui i byte abbiano una quantità di bit diversa da 8)
Xeverous

2

Il codice seguente funziona perfettamente per la maggior parte degli ambienti attuali:

  #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

3
Nota che _WIN64richiede di aver già incluso <windows.h>. Con Visual C ++, è meglio utilizzare il built-in definisce compilatore: _M_IX86, _M_X64, _M_ARM, _M_ARM64, ecc
Chuck Walbourn

Per PowerPC, credo che è necessario verificare la presenza di __ppc64__, __powerpc64__e _ARCH_PPC64. Ciò cattura anche AIX e altre piattaforme.
JWW

1

Se puoi usare le configurazioni di progetto in tutti i tuoi ambienti, ciò semplificherebbe la definizione di un simbolo a 64 e 32 bit. Quindi avresti configurazioni di progetto come questa:

Debug a
32 bit Versione a 32
bit Debug a
64 bit Versione a 64 bit

EDIT: si tratta di configurazioni generiche, non di configurazioni mirate. Chiamali come vuoi.

Se non puoi farlo, mi piace l'idea di Jared.


Oppure combina i due: rileva automaticamente la configurazione sui compilatori che conosci, ma torna a guardare un #define specificato nel progetto / riga di comando / qualunque cosa sui compilatori non riconosciuti.
Steve Jessop,

4
In che modo la tua soluzione specifica per VisualStudio ti aiuterà con la domanda multipiattaforma dell'OP?
alex tingle,

3
@Jon: Hmm. Per definizione, NON sono supportati in nessun tipo di ambiente multipiattaforma . A meno che non sia la definizione MS di multipiattaforma: funziona con versioni più recenti di Windows.
EFraim

1
@EFraim: Sì, puoi TARGET 32- o 64-bit usando VS, ma non è di questo che sto parlando. Le configurazioni di progetto generiche e i nomi che le assegno non hanno assolutamente nulla a che fare con la piattaforma. Se le configurazioni del progetto sono specifiche di VS, è un peccato perché sono molto utili.
Jon Seigel,

1
Penso che questa sia la risposta giusta. È più affidabile che provare a rilevare automaticamente le cose. Tutti gli IDE che ho mai visto supportano questa funzione in qualche modo e scommetto che quelli che non ho mai visto supportano anche questa. Se si utilizza make, o jam, è possibile impostare le variabili dalla riga di comando quando richiamate, nel solito modo.

1

Metterei i sorgenti a 32 e 64 bit in file diversi e quindi selezionerei i file sorgente appropriati usando il sistema di compilazione.


2
Questo sarebbe simile all'avere il sistema di generazione che ti dà un flag come -DBUILD_64BIT. Spesso, alcune cose sono molto simili sia a 32 che a 64 bit, quindi averlo nello stesso file può essere abbastanza pratico.
Alexis Wilke,

Il mantenimento di file twin source è soggetto a errori. IMO anche un enorme #if bit64 .. tutto il codice, per 64 bit #else .. tutto il codice, per 32 bit #endif è meglio di così. (# if's line-by-line è l'ideale a mio avviso)
brewmanz

1

Prendendo in prestito da Contango s' ottima risposta di cui sopra e combinandolo con ' Meglio Macro, meglio Bandiere ' dal perfetto C ++, si può fare:

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

Quindi puoi usarlo come:

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

O usando la macro extra che ho aggiunto:

MYPROJ_IF_64_BIT_ELSE( DoMy64BitOperation(), DoMy32BitOperation() );

0

Sto aggiungendo questa risposta come caso d'uso ed esempio completo per il controllo di runtime descritto in un'altra risposta .

Questo è l'approccio che ho adottato per comunicare all'utente finale se il programma è stato compilato come 64-bit o 32-bit (o altro, del resto):

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

Compila e verifica

g++ -g test.cc
./a.out
My App v0.09 [64-bit]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.