Определение 32 против 64 бит в C ++

136

Я ищу способ надежно определить, компилируется ли код C ++ в 32 против 64 бит. Мы придумали то, что мы считаем разумным решением с использованием макросов, но нам было любопытно узнать, могут ли люди подумать о случаях, когда это может дать сбой, или есть лучший способ сделать это. Обратите внимание, что мы пытаемся сделать это в кросс-платформенной среде с несколькими компиляторами.

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

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

Спасибо.

Джо Коркери
источник
8
Если вы действительно заботитесь о том, каков размер вашей архитектуры, не забывайте о возможности того, что она не 32- и 64-разрядная. Знаете, есть 16- и 128-битные архитектуры.
Алекс Тингл
В чем разница между 64-битной и 32-битной операциями?
peterchen
2
Вы действительно не должны связывать это с шириной слова целевой платформы. Вместо этого используйте размер соответствующих типов данных напрямую, чтобы определить, что делать. stdint.hможет быть вашим другом, или вам может потребоваться разработать некоторые собственные typedefs самостоятельно.
Фил Миллер
Этот тест не работает на Visual Studio 2008 SP1. Он застревает на IS64BIT как для 32-битных, так и для 64-битных.
Контанго

Ответы:

99

К сожалению, нет кроссплатформенного макроса, который бы определял 32/64 бит в основных компиляторах. Я нашел самый эффективный способ сделать это следующим образом.

Сначала я выбираю свое собственное представление. Я предпочитаю ENVIRONMENT64 / ENVIRONMENT32. Затем я выясняю, что все основные компиляторы используют для определения, является ли это 64-битной средой или нет, и использую это для установки моих переменных.

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

Другой более простой способ - просто установить эти переменные из командной строки компилятора.

JaredPar
источник
3
ну, кроме GCC и VS, существуют другие компиляторы. Например, на ум приходят QNX и GHS (хотя я подозреваю, что QNX имеет схожие определения времени сборки с GCC). Также вы забыли MIPS64 и IA64 архитектуры в чеке GCC
Rom
14
@Rom, определенно более двух компиляторов и архитектур. Это всего лишь образец того, как подойти к этой проблеме, а не полное решение.
JaredPar
2
Я говорю "обычно". «В идеале», вероятно, более реалистично.
Стив Джессоп
7
Я думаю, что вы должны использовать "#if определенные ( WIN32 ) || определенные (_WIN64)" и т. Д.
KindDragon
3
#if _WIN32 || _WIN64... #elif __GNUC__... #else # error "Missing feature-test macro for 32/64-bit on this compiler."?
Дэвислор
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
Что будет, если size_t не равен ни 4, ни 8?
Jesper
16
@Jesper, тогда вы получите ошибку ссылки в примере выше. Или вы могли бы реализовать DoMyOperation для этого случая
Кирилл В. Лядвинский
1
Плавное использование шаблонов и похвал для проверки того, что имеет значение (размер какого-то конкретного типа), а не коррелят.
Фил Миллер
2
Осторожнее с использованием size_t для этого. Например, могут возникнуть проблемы, когда он не соответствует размеру указателя (например, на платформах с более чем одним размером указателя).
Логан Капальдо
8
Стандарт говорит, что размер size_tдостаточно велик, чтобы вместить размер любого выделенного объекта в системе. Обычно это то, что вы хотите знать при условной компиляции. Если это не то, что вы хотите, вы можете использовать этот фрагмент с другим типом вместо size_t. Например, это может быть void*.
Кирилл Васильевич Лядвинский
44

К сожалению, в кросс-платформенной среде кросс-компиляции не существует единого надежного способа сделать это исключительно во время компиляции.

  • Оба _WIN32 и _WIN64 иногда и неопределенные, если параметры проекта испорчены или повреждены (особенно на Visual Studio 2008 SP1).
  • Проект с пометкой «Win32» может быть установлен на 64-разрядную версию из-за ошибки конфигурации проекта.
  • В Visual Studio 2008 с пакетом обновления 1 (SP1) иногда intellisense не выделяет правильные части кода в соответствии с текущим #define. Это затрудняет точное определение того, какой #define используется во время компиляции.

Поэтому единственный надежный метод - это объединить 3 простых проверки :

  • 1) настройка времени компиляции и;
  • 2) Проверка времени выполнения и;
  • 3) Надежная проверка времени компиляции .

Простая проверка 1/3: настройка времени компиляции

Выберите любой метод для установки требуемой переменной #define. Я предлагаю метод от @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

Простая проверка 2/3: проверка во время выполнения

В main () дважды проверьте, имеет ли sizeof () смысл:

#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

Простая проверка 3/3: надежная проверка времени компиляции

Общее правило: «каждый #define должен заканчиваться на #else, который генерирует ошибку».

#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

Обновление 2017-01-17

Комментарий от @AI.G:

Через 4 года (не знаю, было ли это возможно раньше) вы можете преобразовать проверку во время выполнения в проверку во время компиляции, используя static assert: static_assert (sizeof (void *) == 4) ;. Теперь все это делается во время компиляции :)

Приложение

Кстати, приведенные выше правила могут быть адаптированы, чтобы сделать всю вашу кодовую базу более надежной:

  • Каждый оператор if () заканчивается на «else», которое генерирует предупреждение или ошибку.
  • Каждый оператор switch () заканчивается значением «default:», которое генерирует предупреждение или ошибку.

Причина того, что это работает хорошо, заключается в том, что он заставляет вас заранее думать о каждом отдельном случае, а не полагаться на (иногда ошибочную) логику в части «else» для выполнения правильного кода.

Я использовал эту технику (среди многих других), чтобы написать 30-тысячный линейный проект, который работал безупречно со дня, когда он был впервые запущен в производство (это было 12 месяцев назад).

Контанго
источник
sizeof(void*)это решается во время компиляции или во время выполнения? если это во время компиляции, то во время выполнения проверка всегда будет if(8!=8){...}.
Амин
@ameen Это решается во время выполнения. Цель этой проверки - убедиться, что программа завершает работу с соответствующей ошибкой, если битность не соответствует ожидаемой. Это означает, что разработчик может немедленно исправить эту ошибку, а не пытаться диагностировать скрытые ошибки, которые появляются позже.
Контанго
3
4 года спустя (не знаю , если это было возможно раньше) , вы можете преобразовать проверку во время выполнения времени компиляции один с использованием статических проверок: static_assert(sizeof(void*) == 4);. Теперь все это делается во время компиляции :)
Ал.Г.
1
static_assert(sizeof(void*) * CHAR_BIT == 32)более выразителен и технически корректен (хотя я не знаю ни одной архитектуры, где байты имеют разное количество битов, чем 8)
Xeverous
1
Смотрите также мой ответ ниже, который сочетает в себе этот отличный ответ с « Лучшими макросами, лучшими флагами » из Fluent C ++.
металл
30

Вы должны быть в состоянии использовать макросы, определенные в stdint.h. В частности INTPTR_MAX, именно то значение, которое вам нужно.

#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

Некоторые (все?) Версии компилятора Microsoft не входят в комплект stdint.h. Не знаю почему, так как это стандартный файл. Вот версия, которую вы можете использовать:http://msinttypes.googlecode.com/svn/trunk/stdint.h

Алекс Тингл
источник
4
Почему нет stdint.h для Microsoft? Потому что он был представлен стандартом C99, и Microsoft, похоже, активно отказывается от реализации даже самых простых вещей из C99. Даже простой библиотечный материал, который не требует смены компилятора. Даже то, что уже делается при компиляции для C ++ (например, объявления после операторов). Я знаю, что он нуждается в тестировании и т. Д., Но я также знаю, что MS получает (или когда-то получает) значительную часть своей библиотеки от Dinkumware / Plauger, а Dinkumware уже много лет работает с библиотекой C99.
Майкл Берр
2
VC ++ 2010 (бета 1, в любом случае) имеет <stdint.h>и <cstdint>. Что касается нынешнего положения вещей - библиотека VC ++ происходит из Dinkumware (все еще делает - TR1 также была взята оттуда), но из того, что я помню, читая в VCBlog, она подвергается довольно значительному рефакторингу для аккуратной компиляции /clr, работы со всеми MSVC нестандартные типы, такие как __int64, и так далее - вот почему это не так просто, как просто взять и поместить в следующую версию компилятора.
Павел Минаев
2
Это привело меня к правильному ответу, но я думаю, что вы должны сравнить с UINT64_MAX, а не с INT64_MAX. Я использовал SIZE_MAX == UINT64_MAX - вероятно, то же самое
Арно Duvenhage
15

Это не будет работать на Windows для начала. Long и int являются 32-битными, независимо от того, компилируете ли вы для 32-битных или 64-битных окон. Я думаю, что проверка размера указателя 8 байт, вероятно, является более надежным маршрутом.

mattnewport
источник
2
К сожалению, sizeof запрещен в директиве #if (если вы думаете об этом, препроцессор не может этого сказать)
EFraim
Да, именно поэтому я оставил это, предлагая проверять размер указателя, а не использовать sizeof - я не могу придумать портативный способ сделать это на макушке моей головы ...
mattnewport
3
Вопрос не (пока) говорит , что это имеет быть сделано во время предварительной обработки. Многие / большинство компиляторов, у которых включена оптимизация, выполнят достойную работу по устранению мертвого кода, даже если вы «оставите его до времени выполнения» с помощью теста, подобного sizeof(void*) == 8 ? Do64Bit() : Do32Bit();. Это может все еще оставить неиспользуемую функцию в двоичном файле, но выражение, скорее всего, скомпилировано только для вызова «правильной» функции.
Стив Джессоп
1
@onebyone, который решает проблему вызовов функций, но что если я захочу объявить переменную другого типа в зависимости от платформы, это нужно будет сделать в препроцессоре, если вы не хотите объявлять несколько переменных и использовать их на основе оператора if ( который также будет оптимизирован, если они не используются, но не будет очень приятным в коде)
Фалаина
1
Тогда вы правы, постоянное выражение в условном выражении не годится. Подход Кирилла может сделать то, что вы хотите, однако: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;
Стив Джессоп
9

Вы могли бы сделать это:

#if __WORDSIZE == 64
char *size = "64bits";
#else
char *size = "32bits";
#endif
Anoop
источник
1
Во многих средах программирования для языков C и C на 64-битных компьютерах переменные типа int по-прежнему имеют ширину 32 бита, но длинные целые и указатели имеют ширину 64 бита. Они описаны как имеющие модель данных 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
источник
7
Этот код неверен. На 64-битной основе определены _WIN32 и _WIN64. Если вы включите его (сначала проверьте _WIN64), это, конечно, работает.
BertR
4

«Скомпилировано в 64 бит» не очень хорошо определено в C ++.

C ++ устанавливает только нижние пределы для таких размеров, как int, long и void *. Нет гарантии, что int является 64-битным, даже если он скомпилирован для 64-битной платформы. Модель позволяет, например, 23 бит intс иsizeof(int *) != sizeof(char *)

Существуют разные модели программирования для 64-битных платформ.

Ваш лучший выбор - тест для конкретной платформы. Ваше второе лучшее портативное решение должно быть более конкретным в том, что является 64-битным.

peterchen
источник
3

Ваш подход не был слишком далеко, но вы только проверяете ли longи intодного и того же размера. Теоретически они могут быть 64-битными, и в этом случае ваша проверка не будет выполнена, предполагая, что оба будут 32-битными. Вот проверка, которая фактически проверяет размер самих типов, а не их относительный размер:

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

В принципе, вы можете сделать это для любого типа, для которого у вас есть системный макрос с максимальным значением.

Обратите внимание, что стандарт long longдолжен быть не менее 64 бит даже в 32-битных системах.

cmaster - восстановить монику
источник
Следует отметить одну вещь: для определения UINT_MAX и ULONG_MAX вы, вероятно, захотите иметь что- #include <limits.h>то перед #ifтестами.
Алексис Уилк
3

Люди уже предложили методы, которые попытаются определить, компилируется ли программа в 32-bitили 64-bit.

И я хочу добавить, что вы можете использовать функцию c ++ 11, static_assertчтобы убедиться, что архитектура соответствует вашим ожиданиям («расслабиться»).

Итак, в месте, где вы определяете макросы:

#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)является более выразительным и технически правильным (хотя я не знаю ни одной архитектуры, где байты имеют разное количество битов, чем 8)
Xeverous
2

Приведенный ниже код отлично работает для большинства современных сред:

  #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
Обратите внимание, что _WIN64требует , чтобы вы уже включили <windows.h>. С помощью Visual C ++, то лучше использовать встроенный компилятор устанавливает: _M_IX86, _M_X64, _M_ARM, _M_ARM64и т.д.
Чак Walbourn
Для PowerPC, я считаю, нужно проверить __ppc64__, __powerpc64__и _ARCH_PPC64. Это ловит AIX и другие платформы тоже.
18-18
1

Если вы можете использовать конфигурации проекта во всех своих средах, это упростит определение 64- и 32-разрядного символа. Таким образом, у вас будут такие конфигурации проекта:

32-битная отладка
32-разрядная версия
64-разрядная отладка
64-разрядная версия

РЕДАКТИРОВАТЬ: это общие конфигурации, а не целевые конфигурации. Называй их как хочешь.

Если ты не можешь сделать это, мне нравится идея Джареда.

Джон Зигель
источник
Или объедините два: автоматически определите конфигурацию компиляторов, о которых вы знаете, но вернитесь к #define, указанному в проекте / командной строке / что-нибудь еще на нераспознанных компиляторах.
Стив Джессоп
4
Как ваше решение для VisualStudio поможет с кроссплатформенным вопросом OP?
Алекс Тингл
3
@ Джон: Хм. Они НЕ поддерживаются в любой кроссплатформенной среде по определению . Если это не определение MS кросс-платформенный - работает на более новых версиях Windows.
EFraim
1
@EFraim: Да, вы можете ЦЕЛИТЬ 32- или 64-битные с использованием VS, но я не об этом говорю. Общие конфигурации проекта и имена, которые я им назначаю, не имеют абсолютно никакого отношения к платформе. Если конфигурации проекта специфичны для VS, то это позор, потому что они очень удобны.
Джон Зигель
1
Я думаю, что это правильный ответ. Это надежнее, чем пытаться автоматически определять вещи. Все IDE, которые я когда-либо видел, поддерживают эту функцию в той или иной форме, и я уверен, что те, которые я никогда не видел, поддерживают ее тоже. Если вы используете make или jam, вы можете установить переменные из командной строки при вызове обычным способом.
1

Я помещал 32-битные и 64-битные источники в разные файлы, а затем выбирал подходящие исходные файлы, используя систему сборки.

большой г
источник
2
Это было бы похоже на то, чтобы система сборки давала вам такой флаг, как -DBUILD_64BIT. Часто некоторые вещи очень похожи как на 32-, так и на 64-битные, поэтому иметь его в одном файле может быть довольно практичным.
Алексис Уилке
Поддержка двойных исходных файлов подвержена ошибкам. ИМО даже огромный #if bit64 .. весь код, для 64-битного #else .. весь код, для 32-битного #endif лучше, чем это. (# если строка построчно идеальна, на мой взгляд)
brewmanz
1

Заимствования из репорт «s отличного ответа выше , и сочетая его с„ Лучше Макросами, Better Flags “из Fluent C ++, вы можете сделать:

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

Тогда вы можете использовать его как:

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

Или используя дополнительный макрос, который я добавил:

MYPROJ_IF_64_BIT_ELSE( DoMy64BitOperation(), DoMy32BitOperation() );
металл
источник
0

Я добавляю этот ответ в качестве варианта использования и завершаю пример проверки выполнения, описанной в другом ответе. .

Это подход, который я использовал для передачи конечному пользователю, была ли программа скомпилирована как 64-битная или 32-битная (или другая, если на то пошло):

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

Компилировать и тестировать

g++ -g test.cc
./a.out
My App v0.09 [64-bit]
vallismortis
источник