Déterminer 32 vs 64 bits en C ++

136

Je cherche un moyen de déterminer de manière fiable si le code C ++ est compilé en 32 vs 64 bits. Nous avons trouvé ce que nous pensons être une solution raisonnable en utilisant des macros, mais nous étions curieux de savoir si les gens pouvaient penser à des cas où cela pourrait échouer ou s'il existe une meilleure façon de le faire. Veuillez noter que nous essayons de le faire dans un environnement multiplateforme et compilateur multiple.

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

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

Merci.

Joe Corkery
la source
8
Si vous vous souciez vraiment de la taille des mots de votre architecture, ne négligez pas la possibilité qu'elle ne soit ni 32 ni 64 bits. Il existe des architectures 16 et 128 bits, vous savez.
alex tingle
Quelle est la différence entre les opérations 64 bits et 32 ​​bits?
peterchen
2
Vous ne devriez vraiment pas conditionner cela sur la largeur de mot de la plate-forme cible. Au lieu de cela, utilisez directement la taille des types de données pertinents pour déterminer ce qu'il faut faire. stdint.hpeut être votre ami, ou vous devrez peut-être développer vos propres typedefs appropriés.
Phil Miller
Ce test ne semble pas fonctionner sur Visual Studio 2008 SP1. Il reste bloqué sur "IS64BIT" pour les 32 bits et 64 bits.
Contango

Réponses:

99

Malheureusement, il n'y a pas de macro multiplateforme qui définit 32/64 bits à travers les principaux compilateurs. J'ai trouvé le moyen le plus efficace de le faire est le suivant.

Je choisis d'abord ma propre représentation. Je préfère ENVIRONMENT64 / ENVIRONMENT32. Ensuite, je découvre ce que tous les principaux compilateurs utilisent pour déterminer s'il s'agit d'un environnement 64 bits ou non et je l'utilise pour définir mes 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

Une autre méthode plus simple consiste simplement à définir ces variables à partir de la ligne de commande du compilateur.

JaredPar
la source
3
eh bien, il existe d'autres compilateurs en plus de GCC et VS. Par exemple, QNX et GHS viennent à l'esprit (bien que je soupçonne que QNX a des définitions de temps de construction similaires à celles de GCC). Vous avez également oublié les architectures MIPS64 et IA64 dans votre vérification GCC
Rom
14
@Rom, certainement plus de 2 compilateurs et architectures. Il s'agit simplement d'un exemple de la façon d'aborder ce problème, et non d'une solution complète.
JaredPar
2
Je dis «habituellement». «Idéalement» est probablement plus réaliste.
Steve Jessop
7
Je pense que vous devriez utiliser "#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
la source
2
Que se passe-t-il si size_t n'est ni 4 ni 8?
Jesper
16
@Jesper, vous obtiendrez alors une erreur de lien dans l'exemple ci-dessus. Ou vous pouvez implémenter DoMyOperation pour ce cas
Kirill V. Lyadvinsky
1
Utilisation judicieuse de modèles et félicitations pour tester ce qui compte (la taille d'un type particulier) plutôt qu'un corrélat.
Phil Miller
2
Attention à utiliser size_t pour cela. Vous pouvez avoir des problèmes où cela ne correspond pas à la taille du pointeur par exemple (par exemple sur les plates-formes avec plus d'une taille de pointeur).
Logan Capaldo
8
Standard dit que la taille de size_test suffisamment grande pour contenir la taille de tout objet alloué dans le système. C'est généralement ce que vous voulez savoir lors de la compilation conditionnelle. Si ce n'est pas ce que vous voulez, vous pouvez utiliser cet extrait de code avec un autre type au lieu de size_t. Par exemple, cela pourrait être void*.
Kirill V. Lyadvinsky
44

Malheureusement, dans un environnement multiplateforme et compilateur croisé, il n'existe pas de méthode fiable unique pour le faire uniquement au moment de la compilation.

  • _WIN32 et _WIN64 peuvent parfois être tous deux indéfinis, si les paramètres du projet sont défectueux ou corrompus (en particulier sur Visual Studio 2008 SP1).
  • Un projet intitulé "Win32" peut être défini sur 64 bits, en raison d'une erreur de configuration du projet.
  • Sur Visual Studio 2008 SP1, l'intellisense ne grise parfois pas les parties correctes du code, selon la #define actuelle. Cela rend difficile de voir exactement quelle #define est utilisée au moment de la compilation.

Par conséquent, la seule méthode fiable consiste à combiner 3 vérifications simples :

  • 1) réglage de l'heure de compilation , et;
  • 2) contrôle d'exécution , et;
  • 3) Vérification robuste du temps de compilation .

Contrôle simple 1/3: réglage de l'heure de compilation

Choisissez n'importe quelle méthode pour définir la variable #define requise. Je suggère la méthode 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

Contrôle simple 2/3: contrôle d'exécution

Dans main (), revérifiez pour voir si sizeof () a du sens:

#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

Vérification simple 3/3: Vérification robuste du temps de compilation

La règle générale est "chaque #define doit se terminer par un #else qui génère une erreur".

#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

Mise à jour 2017-01-17

Commentaire de @AI.G:

4 ans plus tard (je ne sais pas si c'était possible auparavant), vous pouvez convertir la vérification à l'exécution en une vérification à la compilation en utilisant static assert: static_assert (sizeof (void *) == 4) ;. Maintenant, tout est fait au moment de la compilation :)

Annexe A

Incidemment, les règles ci-dessus peuvent être adaptées pour rendre l'ensemble de votre base de code plus fiable:

  • Chaque instruction if () se termine par un "else" qui génère un avertissement ou une erreur.
  • Chaque instruction switch () se termine par un "default:" qui génère un avertissement ou une erreur.

La raison pour laquelle cela fonctionne bien est que cela vous oblige à penser à chaque cas à l'avance, et à ne pas vous fier à la logique (parfois imparfaite) de la partie «else» pour exécuter le code correct.

J'ai utilisé cette technique (parmi beaucoup d'autres) pour écrire un projet de 30 000 lignes qui a parfaitement fonctionné depuis le jour où il a été déployé pour la première fois en production (il y a 12 mois).

Contango
la source
sizeof(void*)est-il résolu au moment de la compilation ou de l'exécution? si c'est au moment de la compilation, la vérification sera toujours au moment de l'exécution if(8!=8){...}.
Ameen
@ameen C'est résolu au moment de l'exécution. Le but de cette vérification est de s'assurer que le programme se termine avec une erreur appropriée si le bitness n'est pas celui attendu. Cela signifie que le développeur peut corriger cette erreur immédiatement, plutôt que d'essayer de diagnostiquer des bogues subtils qui apparaissent plus tard.
Contango
3
4 ans plus tard (je ne sais pas s'il était possible avant) , vous pouvez convertir le contrôle d' exécution pour la compilation en utilisant un assert statique: static_assert(sizeof(void*) == 4);. Maintenant, tout est fait au moment de la compilation :)
Al.G.
1
static_assert(sizeof(void*) * CHAR_BIT == 32)est plus expressif et techniquement correct (bien que je ne connaisse aucune architecture où les octets ont une quantité de bits différente de 8)
Xeverous
1
Voir aussi ma réponse ci - dessous qui combine cette excellente réponse avec " Better Macros, Better Flags " de Fluent C ++.
métal
30

Vous devriez pouvoir utiliser les macros définies dans stdint.h. En particulier, INTPTR_MAXc'est exactement la valeur dont vous avez besoin.

#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

Certaines (toutes?) Versions du compilateur de Microsoft ne sont pas fournies avec stdint.h. Je ne sais pas pourquoi, car c'est un fichier standard. Voici une version que vous pouvez utiliser:http://msinttypes.googlecode.com/svn/trunk/stdint.h

alex tingle
la source
4
Pourquoi pas de stdint.h pour Microsoft? Parce qu'il a été introduit avec la norme C99, et Microsoft semble avoir une aversion active pour la mise en œuvre même des éléments les plus simples de C99. Même les éléments de bibliothèque simples qui ne nécessitent aucun changement de compilateur. Même ce qui est déjà fait lors de la compilation pour C ++ (comme les déclarations après les instructions). Je sais qu'il a besoin d'être testé, etc., mais je sais aussi que MS obtient (ou une fois) une bonne partie de sa bibliothèque de Dinkumware / Plauger, et que Dinkumware avait la bibliothèque C99 depuis des années.
Michael Burr
2
VC ++ 2010 (bêta 1, de toute façon) a <stdint.h>et<cstdint> . En ce qui concerne l'état actuel des choses - la bibliothèque VC ++ provient de Dinkumware (le fait toujours - TR1 a également été pris à partir de là), mais d'après ce que je me souviens avoir lu sur VCBlog, il subit une refactorisation assez importante pour compiler proprement avec /clr, travailler avec tous les MSVC des types non standard comme __int64, et ainsi de suite - c'est pourquoi ce n'est pas aussi simple que de le prendre et de le mettre dans la prochaine version du compilateur.
Pavel Minaev
2
Cela m'a conduit à la bonne réponse, mais je pense que vous devriez comparer à UINT64_MAX et non à INT64_MAX. J'ai utilisé SIZE_MAX == UINT64_MAX - prob the same
Arno Duvenhage
15

Cela ne fonctionnera pas sous Windows pour un début. Les entiers et les entiers sont tous deux 32 bits, que vous compiliez pour des fenêtres 32 bits ou 64 bits. Je pense que vérifier si la taille d'un pointeur est de 8 octets est probablement une route plus fiable.

mattnewport
la source
2
Malheureusement, sizeof est interdit dans la directive #if (si vous y pensez, le préprocesseur n'a aucun moyen de le dire)
EFraim
Oui, c'est pourquoi je l'ai laissé en suggérant de vérifier la taille d'un pointeur plutôt que d'utiliser sizeof - je ne peux pas penser à un moyen portable de le faire du haut de ma tête ...
mattnewport
3
La question ne dit pas (encore) que cela doit être fait au moment du pré-processeur. La plupart / la plupart des compilateurs avec l'optimisation feront un travail décent pour éliminer le code mort, même si vous le «laissez jusqu'à l'exécution» avec un test comme sizeof(void*) == 8 ? Do64Bit() : Do32Bit();. Cela pourrait encore laisser une fonction inutilisée dans le binaire, mais l'expression est probablement compilée juste pour un appel à la fonction «droite».
Steve Jessop
1
@onebyone qui résout le problème des appels de fonction, mais que se passe-t-il si je veux déclarer une variable d'un type différent en fonction de la plate-forme, cela devrait être fait au niveau du préprocesseur à moins que vous ne souhaitiez déclarer plusieurs variables et les utiliser en fonction d'une instruction if ( qui seraient également optimisés s'ils ne sont pas utilisés, mais ne seraient pas très agréables dans le code)
Falaina
1
Alors vous avez raison, une expression constante dans un conditionnel n'est pas bonne. L'approche de Kirill peut faire ce que vous voulez, cependant: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

Vous pouvez faire ceci:

#if __WORDSIZE == 64
char *size = "64bits";
#else
char *size = "32bits";
#endif
Anoop
la source
1
Dans de nombreux environnements de programmation pour les langages dérivés C et C sur des machines 64 bits, les variables "int" ont toujours une largeur de 32 bits, mais les entiers longs et les pointeurs ont une largeur de 64 bits. Ceux-ci sont décrits comme ayant un modèle de données 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
la source
7
Ce code n'est pas correct. Sur 64 bits, _WIN32 et _WIN64 sont définis. Si vous le retournez (vérifiez d'abord _WIN64), cela fonctionne bien sûr.
BertR
4

«Compilé en 64 bits» n'est pas bien défini en C ++.

C ++ définit uniquement des limites inférieures pour des tailles telles que int, long et void *. Il n'y a aucune garantie que int soit 64 bits même lorsqu'il est compilé pour une plate-forme 64 bits. Le modèle permet par exemple 23 bits ints etsizeof(int *) != sizeof(char *)

Il existe différents modèles de programmation pour les plates-formes 64 bits.

Votre meilleur pari est un test spécifique à la plateforme. Votre deuxième meilleure décision portable doit être plus spécifique dans ce qui est 64 bits.

Peterchen
la source
3

Votre approche n'était pas trop éloignée, mais vous ne faites que vérifier si longet intsont de la même taille. Théoriquement, ils pourraient tous les deux être 64 bits, auquel cas votre vérification échouerait, en supposant que les deux soient 32 bits. Voici une vérification qui vérifie en fait la taille des types eux-mêmes, pas leur taille relative:

#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 principe, vous pouvez le faire pour tout type pour lequel vous avez une macro définie par le système avec la valeur maximale.

Notez que la norme exige long longau moins 64 bits, même sur les systèmes 32 bits.

cmaster - réintégrer monica
la source
Une chose à noter, pour que UINT_MAX et ULONG_MAX soient définis, vous voudrez probablement avoir #include <limits.h>quelque part avant vos #iftests.
Alexis Wilke
3

Les gens ont déjà suggéré des méthodes qui tenteront de déterminer si le programme est en cours de compilation dans 32-bitou 64-bit.

Et je veux ajouter que vous pouvez utiliser la fonctionnalité c ++ 11 static_assertpour vous assurer que l'architecture est ce que vous pensez qu'elle est ("se détendre").

Donc, à l'endroit où vous définissez les 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
la source
static_assert(sizeof(void*) * CHAR_BIT == 32)est plus expressif et techniquement correct (bien que je ne connaisse aucune architecture où les octets ont une quantité de bits différente de 8)
Xeverous
2

Le code ci-dessous fonctionne bien pour la plupart des environnements actuels:

  #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
la source
3
Notez que cela _WIN64nécessite que vous ayez déjà inclus <windows.h>. Avec Visual C ++, il est préférable d'utiliser le haut-compilateur définit: _M_IX86, _M_X64, _M_ARM, _M_ARM64, etc.
Chuck Walbourn
Pour PowerPC, je crois que vous devez vérifier __ppc64__, __powerpc64__et _ARCH_PPC64. Cela attrape également AIX et d'autres plates-formes.
jww
1

Si vous pouvez utiliser des configurations de projet dans tous vos environnements, cela facilitera la définition d'un symbole 64 et 32 ​​bits. Vous auriez donc des configurations de projet comme celle-ci:

Débogage
32 bits Version 32
bits Débogage
64 bits Version 64 bits

EDIT: Ce sont des configurations génériques, pas des configurations ciblées. Appelez-les comme vous voulez.

Si vous ne pouvez pas faire ça, j'aime l'idée de Jared.

Jon Seigel
la source
Ou combinez les deux: détectez automatiquement la configuration sur les compilateurs que vous connaissez, mais revenez à la recherche d'une #define spécifiée dans le projet / ligne de commande / quoi que ce soit sur des compilateurs non reconnus.
Steve Jessop
4
Comment votre solution spécifique à VisualStudio va-t-elle vous aider avec la question multiplateforme de l'OP?
alex tingle
3
@Jon: Hmm. Ils ne sont PAS pris en charge dans aucun type d'environnement multiplateforme par définition . À moins que ce ne soit la définition de multi-plateforme de MS - fonctionne sur les nouvelles versions de Windows.
EFraim
1
@EFraim: Oui, vous pouvez CIBLER 32 ou 64 bits en utilisant VS, mais ce n'est pas ce dont je parle. Les configurations de projet génériques, et les noms que je leur attribue, n'ont absolument rien à voir avec la plate-forme. Si les configurations de projet sont spécifiques à VS, c'est dommage car elles sont très pratiques.
Jon Seigel
1
Je pense que c'est la bonne réponse. C'est plus fiable que d'essayer de détecter automatiquement les choses. Tous les IDE que j'ai vus prennent en charge cette fonctionnalité sous une forme ou une autre, et je parie que ceux que je n'ai jamais vus la prennent également en charge. Si vous utilisez make ou jam, vous pouvez définir les variables à partir de la ligne de commande lorsqu'elle est appelée, de la manière habituelle.
1

Je place les sources 32 bits et 64 bits dans différents fichiers, puis je sélectionne les fichiers source appropriés à l'aide du système de construction.

big-z
la source
2
Ce serait similaire à ce que le système de construction vous donne un indicateur tel que -DBUILD_64BIT. Souvent, certaines choses sont très similaires à 32 et 64 bits, donc l'avoir dans le même fichier peut être très pratique.
Alexis Wilke
La maintenance des fichiers source jumeaux est sujette aux erreurs. IMO même un énorme #if bit64 .. tout le code, pour 64 bits #else .. tout le code, pour 32 bits #endif est mieux que cela. (# if ligne par ligne est idéal à mon avis)
brewmanz
1

En empruntant à l' excellente réponse de Contango ci - dessus et en la combinant avec " Better Macros, Better Flags " de Fluent C ++, vous pouvez faire:

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

Ensuite, vous pouvez l'utiliser comme:

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

Ou en utilisant la macro supplémentaire que j'ai ajoutée:

MYPROJ_IF_64_BIT_ELSE( DoMy64BitOperation(), DoMy32BitOperation() );
métal
la source
0

J'ajoute cette réponse comme cas d'utilisation et exemple complet pour le contrôle d'exécution décrit dans une autre réponse .

C'est l'approche que j'ai adoptée pour indiquer à l'utilisateur final si le programme a été compilé en 64 bits ou 32 bits (ou autre, d'ailleurs):

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

Compiler et tester

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