Determinando 32 vs 64 bits em C ++

136

Eu estou procurando uma maneira de determinar com segurança se o código C ++ está sendo compilado em 32 vs 64 bits. Chegamos ao que pensamos ser uma solução razoável usando macros, mas estávamos curiosos para saber se as pessoas poderiam pensar em casos em que isso pode falhar ou se há uma maneira melhor de fazer isso. Observe que estamos tentando fazer isso em um ambiente compilador de múltiplas plataformas.

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

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

Obrigado.

Joe Corkery
fonte
8
Se você realmente se importa com o tamanho da palavra da sua arquitetura, não negligencie a possibilidade de que não seja nem 32 nem 64 bits. Existem arquiteturas de 16 e 128 bits por aí, você sabe.
alex formigamento
Qual é a diferença entre a operação de 64 bits e a de 32 bits?
Peterchen
2
Você realmente não deve condicionar isso na largura da palavra da plataforma de destino. Em vez disso, use o tamanho dos tipos de dados relevantes diretamente para determinar o que fazer. stdint.hpode ser seu amigo ou pode ser necessário desenvolver alguns tipos de dados apropriados.
Phil Miller
Este teste parece não funcionar no Visual Studio 2008 SP1. Ele fica preso no "IS64BIT" para 32 e 64 bits.
Contango 9/09/12

Respostas:

99

Infelizmente, não há macro de plataforma cruzada que defina 32/64 bits nos principais compiladores. Eu descobri que a maneira mais eficaz de fazer isso é o seguinte.

Primeiro eu escolho minha própria representação. Prefiro AMBIENTE64 / AMBIENTE32. Depois, descubro o que todos os principais compiladores usam para determinar se é um ambiente de 64 bits ou não e o uso para definir minhas variáveis.

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

Outra rota mais fácil é simplesmente definir essas variáveis ​​na linha de comando do compilador.

JaredPar
fonte
3
bem, existem outros compiladores além do GCC e do VS. Por exemplo, QNX e GHS vêm à mente (embora eu suspeite que o QNX tenha definições de tempo de construção semelhantes ao GCC). Além disso, você esqueceu MIPS64 e IA64 arquiteturas em seu cheque GCC
Rom
14
@ Rom, definitivamente mais de 2 compiladores e arquiteturas. Este é apenas um exemplo de como abordar esse problema, não uma solução completa.
JaredPar
2
Eu digo "normalmente". "Idealmente" é provavelmente mais realista.
21607 Steve Jessop #
7
Eu acho que você deve usar "#if definido ( WIN32 ) || definido (_WIN64)" etc 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
fonte
2
O que acontece se o size_t não for 4 nem 8?
Jesper
16
@ Jesper, então você receberá um erro de link no exemplo acima. Ou você poderia implementar DoMyOperation para esse caso
Kirill V. Lyadvinsky
1
Uso inteligente de modelos e elogios para testar o que importa (o tamanho de algum tipo específico) em vez de um correlato.
Phil Miller
2
Cuidado ao usar size_t para isso. Você pode ter problemas em que ele não corresponde ao tamanho do ponteiro, por exemplo (por exemplo, em plataformas com mais de um tamanho).
Logan Capaldo
8
O Standard diz que o tamanho de size_té grande o suficiente para armazenar o tamanho de qualquer objeto alocado no sistema. Geralmente é o que você deseja saber enquanto compila condicionalmente. Se não é o que você deseja, você pode usar esse snippet com outro tipo em vez de size_t. Por exemplo, poderia ser void*.
227 Kirill V. Lyadvinsky
44

Infelizmente, em um ambiente de plataforma cruzada e compilador cruzado, não existe um método confiável único para fazer isso exclusivamente em tempo de compilação.

  • Ambos _WIN32 e _WIN64 às vezes pode tanto ser indefinido, se as configurações do projeto são falhos ou corrompido (particularmente no Visual Studio 2008 SP1).
  • Um projeto chamado "Win32" pode ser definido como 64 bits, devido a um erro de configuração do projeto.
  • No Visual Studio 2008 SP1, às vezes o intellisense não esmaece as partes corretas do código, de acordo com o atual #define. Isso dificulta ver exatamente qual #define está sendo usado em tempo de compilação.

Portanto, o único método confiável é combinar 3 verificações simples :

  • 1) Configuração do tempo de compilação , e;
  • 2) verificação de tempo de execução e;
  • 3) Verificação robusta do tempo de compilação .

Verificação simples 1/3: configuração do tempo de compilação

Escolha qualquer método para definir a variável #define necessária. Sugiro o 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

Verificação simples 2/3: verificação de tempo de execução

Em main (), verifique novamente se sizeof () faz 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

Verificação simples 3/3: verificação robusta do tempo de compilação

A regra geral é "todo #define deve terminar em um #else que gera um erro".

#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

Atualização 2017-01-17

Comentário de @AI.G:

4 anos depois (não sei se era possível antes), você pode converter a verificação em tempo de execução para uma em tempo de compilação usando a afirmação estática: static_assert (sizeof (void *) == 4) ;. Agora tudo é feito em tempo de compilação :)

Apêndice A

Incidencialmente, as regras acima podem ser adaptadas para tornar toda a sua base de código mais confiável:

  • Cada instrução if () termina em um "else" que gera um aviso ou erro.
  • Cada instrução switch () termina com um "padrão:" que gera um aviso ou erro.

A razão pela qual isso funciona bem é que obriga você a pensar em todos os casos com antecedência e a não confiar na lógica (às vezes falha) da parte "else" para executar o código correto.

Usei essa técnica (entre muitas outras) para escrever um projeto de 30.000 linhas que funcionou perfeitamente desde o primeiro dia em que foi implantado na produção (há 12 meses).

Contango
fonte
sizeof(void*)é resolvido em tempo de compilação ou tempo de execução? se estiver em tempo de compilação, em tempo de execução, a verificação sempre será if(8!=8){...}.
Ameen
@ameen Foi resolvido em tempo de execução. O objetivo dessa verificação é garantir que o programa saia com um erro apropriado se a testemunha não for o esperado. Isso significa que o desenvolvedor pode corrigir esse erro imediatamente, em vez de tentar diagnosticar erros sutis que aparecem mais tarde.
Contango 31/05
3
4 anos depois (não sei se era possível antes) você pode converter a verificação de tempo de execução para compilar-time um usando assert estática: static_assert(sizeof(void*) == 4);. Agora tudo é feito em tempo de compilação :)
Al.G.
1
static_assert(sizeof(void*) * CHAR_BIT == 32)é mais expressiva e tecnicamente correta (embora eu não sei qualquer arquitetura onde bytes tem uma quantidade diferente de bits de 8)
Xeverous
1
Veja também minha resposta abaixo, que combina essa excelente resposta com " Melhores macros, melhores sinalizadores " do Fluent C ++.
metal
30

Você deve poder usar as macros definidas em stdint.h. Em particular, INTPTR_MAXé exatamente o valor que você precisa.

#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

Algumas versões (todas?) Do compilador da Microsoft não são fornecidas stdint.h. Não sei por que, já que é um arquivo padrão. Aqui está uma versão que você pode usar:http://msinttypes.googlecode.com/svn/trunk/stdint.h

alex formiga
fonte
4
Por que não stdint.h para a Microsoft? Por ter sido introduzido com o padrão C99, a Microsoft parece ter uma aversão ativa à implementação do mais fácil dos itens do C99. Mesmo o material de biblioteca fácil que não requer alteração do compilador. Até o que já está sendo feito ao compilar para C ++ (como declarações após instruções). Sei que precisa de testes, etc., mas também sei que a MS recebe (ou já recebeu) uma boa parte de sua biblioteca da Dinkumware / Plauger, e a Dinkumware mantinha o material da biblioteca C99 por anos.
227 Michael Burr
2
O VC ++ 2010 (beta 1, de qualquer maneira) possui <stdint.h>e <cstdint>. Quanto ao estado atual - a biblioteca VC ++ é originária do Dinkumware (ainda existe - o TR1 também foi retirado de lá), mas pelo que me lembro de ter lido no VCBlog, ele passa por uma refatoração bastante significativa para compilar corretamente /clr, trabalhar com todos os MSVC tipos não-padrão como __int64, e assim por diante - e é por isso que não é tão simples quanto levá-lo e colocá-lo na próxima versão do compilador.
Pavel Minaev 01/10/09
2
Isso me levou à resposta correta, mas acho que você deve comparar com UINT64_MAX e não INT64_MAX. Eu costumava SIZE_MAX == UINT64_MAX - prob o mesmo
Arno Duvenhage
15

Isso não funcionará no Windows para começar. Longs e ints são ambos de 32 bits, esteja você compilando para janelas de 32 ou 64 bits. Eu acho que verificar se o tamanho de um ponteiro é 8 bytes é provavelmente uma rota mais confiável.

mattnewport
fonte
2
Infelizmente sizeof é proibido na directiva #if (se você pensar nisso pré-processador não tem nenhuma maneira de dizer)
EFRAIM
Sim, é por isso que eu deixei em sugerindo a verificação do tamanho de um ponteiro em vez de usar sizeof - Eu não posso pensar de uma maneira portátil para fazê-lo fora do topo da minha cabeça ...
mattnewport
3
A pergunta ainda não diz que precisa ser feita no momento do pré-processador. Muitos / a maioria dos compiladores com otimização ativada fará um trabalho decente ao eliminar o código morto, mesmo que você "o ​​deixe até o tempo de execução" com um teste como esse sizeof(void*) == 8 ? Do64Bit() : Do32Bit();. Isso ainda pode deixar uma função não utilizada no binário, mas a expressão provavelmente é compilada apenas para uma chamada para a função "correta".
21607 Steve Jessop #
1
@onebyoneone que resolve o problema de chamadas de função, mas e se eu quiser declarar uma variável com um tipo diferente com base na plataforma, isso precisa ser feito no pré-processador, a menos que você queira declarar várias variáveis ​​e usá-las com base na instrução if ( que também seria otimizado se não fossem usados, mas não seria muito agradável no código)
Falaina
1
Então você está certo, uma expressão constante em uma condicional não é boa. A abordagem de Kirill pode fazer o que você quiser: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

Você poderia fazer isso:

#if __WORDSIZE == 64
char *size = "64bits";
#else
char *size = "32bits";
#endif
Anoop
fonte
1
Em muitos ambientes de programação para linguagens derivadas de C e C em máquinas de 64 bits, as variáveis ​​"int" ainda têm 32 bits de largura, mas números inteiros longos e ponteiros têm 64 bits de largura. Eles são descritos como tendo um modelo de dados 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
fonte
7
Este código não está correto. Em 64 bits, _WIN32 e _WIN64 são definidos. Se você mudar (primeiro verifique _WIN64), é claro que funciona.
22412 BertR
4

"Compilado em 64 bits" não está bem definido em C ++.

O C ++ define apenas limites inferiores para tamanhos como int, long e void *. Não há garantia de que int seja de 64 bits, mesmo quando compilado para uma plataforma de 64 bits. O modelo permite a por exemplo 23 bits ints esizeof(int *) != sizeof(char *)

Existem diferentes modelos de programação para plataformas de 64 bits.

Sua melhor aposta é um teste específico da plataforma. Sua segunda melhor decisão portátil deve ser mais específica no que é 64 bits.

peterchen
fonte
3

Sua abordagem não foi muito distante, mas você está apenas verificando se longe inttem o mesmo tamanho. Teoricamente, os dois poderiam ter 64 bits; nesse caso, sua verificação falharia, assumindo que ambos eram 32 bits. Aqui está uma verificação que realmente verifica o tamanho dos tipos em si, não o tamanho 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

Em princípio, você pode fazer isso para qualquer tipo para o qual você tenha uma macro definida pelo sistema com o valor máximo.

Observe que o padrão exige long longpelo menos 64 bits, mesmo em sistemas de 32 bits.

cmaster - restabelece monica
fonte
Uma coisa a ser observada: para que UINT_MAX e ULONG_MAX sejam definidos, você provavelmente deseja ter #include <limits.h>algum lugar antes dos #iftestes.
Alexis Wilke #
3

As pessoas já sugeriram métodos que tentarão determinar se o programa está sendo compilado em 32-bitou 64-bit.

E quero acrescentar que você pode usar o recurso c ++ 11 static_assertpara garantir que a arquitetura seja o que você pensa que é ("relaxar").

Portanto, no local em que você define as 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
fonte
static_assert(sizeof(void*) * CHAR_BIT == 32)é mais expressiva e tecnicamente correta (embora eu não sei qualquer arquitetura onde bytes tem uma quantidade diferente de bits de 8)
Xeverous
2

O código abaixo funciona bem para a maioria dos ambientes atuais:

  #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
fonte
3
Observe que _WIN64requer que você já tenha incluído <windows.h>. Com o Visual C ++, é melhor usar o built-in define compilador: _M_IX86, _M_X64, _M_ARM, _M_ARM64, etc.
Chuck Walbourn
Para PowerPC, eu acredito que você precisa para verificar se __ppc64__, __powerpc64__e _ARCH_PPC64. Isso também captura o AIX e outras plataformas.
JWW
1

Se você puder usar as configurações do projeto em todos os seus ambientes, isso facilitaria a definição de um símbolo de 64 e 32 bits. Então você teria configurações de projeto como esta:

Depuração de
32 bits Liberação de
32 bits Depuração de 64 bits
64 bits Versão de 64 bits

EDIT: Estas são configurações genéricas, não configurações direcionadas. Ligue para eles como quiser.

Se você não pode fazer isso, eu gosto da ideia de Jared.

Jon Seigel
fonte
Ou combine os dois: detecte automaticamente a configuração nos compiladores que você conhece, mas volte a olhar para um #define especificado no projeto / linha de comando / o que quer que seja nos compiladores não reconhecidos.
21607 Steve Jessop #
4
Como sua solução específica do VisualStudio vai ajudar na pergunta de plataforma cruzada do OP?
alex formigamento
3
@ Jon: Hmm. Eles NÃO são suportados em qualquer tipo de ambiente de plataforma cruzada por definição . A menos que seja a definição de plataforma cruzada da MS - funciona com os novos sabores do Windows.
EFraim
1
@ FEraim: Sim, você pode segmentar 32 ou 64 bits usando o VS, mas não é disso que estou falando. As configurações genéricas do projeto e os nomes que eu lhes atribuo não têm absolutamente nada a ver com plataforma. Se as configurações do projeto são específicas do VS, isso é uma pena, porque elas são muito úteis.
9119 Jon Seigel
1
Eu acho que essa é a resposta certa. É mais confiável do que tentar detectar coisas automaticamente. Todos os IDEs que já vi suportam esse recurso de alguma forma, e aposto que os que nunca vi também o suportam. Se você usa make ou jam, é possível definir as variáveis ​​na linha de comando quando invocadas, da maneira usual.
1

Colocaria fontes de 32 e 64 bits em arquivos diferentes e depois selecionaria os arquivos de origem apropriados usando o sistema de compilação.

big-z
fonte
2
Isso seria semelhante a ter o sistema de construção dando a você uma bandeira como -DBUILD_64BIT. Freqüentemente, certas coisas são muito semelhantes às de 32 e 64 bits, portanto, tê-lo no mesmo arquivo pode ser bastante prático.
Alexis Wilke
A manutenção de arquivos de origem dupla é suscetível a erros. Na IMO, até mesmo um enorme #if bit64 .. todo código, para 64 bits #else .. todo código, para 32 bits #endif é melhor que isso. (# se da linha-a-linha é ideal, na minha opinião)
brewmanz
1

Tomando emprestado a excelente resposta do Contango acima e combinando-a com " Melhores macros, melhores sinalizadores " do Fluent C ++, você pode:

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

Então você pode usá-lo como:

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

Ou usando a macro extra que eu adicionei:

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

Estou adicionando esta resposta como um caso de uso e exemplo completo para a verificação de tempo de execução descrita em outra resposta .

Esta é a abordagem que eu tenho adotado para transmitir ao usuário final se o programa foi compilado como 64 bits ou 32 bits (ou outro, nesse 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 e testar

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