Визначення 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-бітні архітектури.
alex tingle

Яка різниця між 64-бітною та 32-бітовою операцією?
peterchen

2
Ви дійсно не повинні обумовлювати це на ширині слова цільової платформи. Натомість використовуйте розмір відповідних типів даних безпосередньо, щоб визначити, що робити. stdint.hможе бути вашим другом, або вам може знадобитися розробити деякі відповідні ваші власні типи.
Філ Міллер

Цей тест, здається, не працює на Visual Studio 2008 SP1. Він застрягає на "IS64BIT" як для 32-розрядної, так і для 64-розрядної.
Контанго

Відповіді:


99

На жаль, не існує крос-платформного макросу, який визначає 32/64 біт для основних компіляторів. Я знайшов найефективніший спосіб зробити це наступним.

Спочатку я вибираю власне представлення. Я віддаю перевагу ЕКОЛОГІЇ64 / ЕКОЛОГІЯ32. Потім я з’ясовую, що всі основні компілятори використовують для визначення, чи це 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

Ще один простіший спосіб - просто встановити ці змінні з командного рядка компілятора.


3
ну, є й інші компілятори, окрім GCC та VS. Наприклад, приходять до уваги QNX та GHS (хоча я підозрюю, що QNX визначає час створення, подібний до GCC). Також ви забули архітектури MIPS64 та IA64 у своєму чеку на GCC
Ром

14
@Rom, безумовно, більше 2 компіляторів та архітектури. Це просто призначено для вибірки того, як підійти до цієї проблеми, а не до повного рішення.
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
Плавне використання шаблонів і kudos для тестування того, що має значення (розмір якогось певного типу), а не співвідношення.
Філ Міллер

2
Обережно з використанням size_t для цього. Ви можете мати проблеми, коли він не відповідає розміру вказівника, наприклад (наприклад, на платформах з більш ніж одним розміром вказівника).
Логан Капальдо

8
Стандарт говорить про те, що розмір size_tдосить великий, щоб вмістити розмір будь-якого виділеного об'єкта в системі. Зазвичай це те, що ви хочете знати при умовному складанні. Якщо це не те, що ви хочете, ви можете використовувати цей фрагмент замість іншого типу size_t. Наприклад, це могло бути void*.
Кирило В. Лядвінський

44

На жаль, у крос-платформі, середовищі компіляції, не існує єдиного надійного способу зробити це чисто під час компіляції.

  • Обидва _WIN32 і _WIN64 іноді і невизначені, якщо параметри проекту зіпсовані або пошкоджені (особливо на Visual Studio 2008 SP1).
  • Проект із позначкою "Win32" міг бути встановлений на 64-бітне через помилку конфігурації проекту.
  • У Visual Studio 2008 SP1 іноді інтелігенція не витісняє правильні частини коду, відповідно до поточного #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 (sizeof (void *) == 4) ;. Тепер це все зроблено під час компіляції :)

Додаток А

До речі, вищезазначені правила можна адаптувати, щоб зробити всю вашу кодову базу надійнішою:

  • Кожен оператор if () закінчується на "else", що генерує попередження або помилку.
  • Кожен оператор switch () закінчується на "за замовчуванням:", що створює попередження або помилку.

Причина, чому це працює добре, полягає в тому, що вона змушує заздалегідь продумати кожен окремий випадок, а не покладатися на (іноді помилкову) логіку в частині "else" для виконання правильного коду.

Я використав цю техніку (серед багатьох інших), щоб написати проект на 30 000 ліній, який бездоганно працював з дня його першого введення у виробництво (це було 12 місяців тому).


sizeof(void*)це вирішено під час компіляції або під час виконання? якщо він знаходиться в час компіляції, то під час виконання перевірка завжди буде if(8!=8){...}.
Ameen

@ameen Це вирішено під час виконання. Мета цієї перевірки - переконатися, що програма виходить із відповідною помилкою, якщо біт - це не те, що очікується. Це означає, що розробник може негайно виправити цю помилку, а не намагатися діагностувати найтонші помилки, які з’являються згодом.
Контанго

3
4 роки по тому (не знаю , якщо це було можливо раніше) , ви можете перетворити перевірку у час виконання часу компіляції один з використанням статичних перевірок: static_assert(sizeof(void*) == 4);. Тепер це все зроблено в час компіляції :)
Al.G.

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 - те ж саме
Arno Duvenhage

15

Це не буде працювати в Windows для початку. Longs і int - це 32 біти, незалежно від того, чи збираєте ви для 32-бітного або 64-бітного Windows. Я б подумав перевірити, чи розмір вказівника становить 8 байт, мабуть, більш надійний маршрут.


2
На жаль, sizeof заборонено в директиві #if (якщо ви думаєте про це, препроцесор не має можливості сказати)
EFraim

Так, тому я залишив це, запропонувавши перевірити розмір вказівника, а не використовувати sizeof - я не можу придумати портативний спосіб зробити це вгорі голови ...
mattnewport

3
Питання не (поки) говорить , що це має бути зроблено під час попередньої обробки. Багато / більшість компіляторів з оптимізацією будуть робити гідну роботу з усунення мертвого коду, навіть якщо ви «залишите його до часу запуску» з таким тестом sizeof(void*) == 8 ? Do64Bit() : Do32Bit();. Це все ще може залишити невикористану функцію у двійковому, але вираз, ймовірно, складений просто для виклику функції "правильний".
Стів Джессоп

1
@onebyone, який вирішує проблему викликів функцій, але що робити, якщо я хочу оголосити змінну іншого типу на основі платформи, це потрібно зробити в препроцесорі, якщо ви не хочете оголосити кілька змінних і використовувати їх на основі оператора if ( які також були б оптимізовані, якщо вони не використовуються, але не будуть дуже приємними в коді)
Falaina

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

1
У багатьох середовищах програмування для мов C та C на 64-розрядних машинах змінні "int" все ще є 32-бітними, але довгі цілі числа та покажчики - 64-бітні. Вони описані як модель даних LP64. unix.org/version2/whatsnew/lp64_wp.html
Гермес

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
Цей код невірний. На 64-бітному рівні визначаються і _WIN32, і _WIN64. Якщо ви повернете його (спочатку перевірте на _WIN64), це, звичайно, працює.
BertR

4

"Зібрано в 64 біт" недостатньо визначено в C ++.

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

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

Ваша найкраща ставка - це тест, визначений на платформі. Ваше друге найкраще портативне рішення повинно бути більш конкретним у тому, що таке 64 біт.


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-бітних системах.


Варто зазначити одне, що для визначення 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 та інші платформи.
jww

1

Якщо ви можете використовувати конфігурації проектів у всіх своїх середовищах, це полегшить визначення 64- та 32-бітного символу. Отже, у вас буде така конфігурація проекту:

32-розрядний налагодження
32-розрядний випуск
64-розрядний налагодження
64-розрядний випуск

EDIT: Це загальні конфігурації, а не цільові конфігурації. Телефонуйте їм, що завгодно.

Якщо ви не можете цього зробити, мені подобається ідея Джареда.


Або комбінуйте два: автоматично визначте конфігурацію компіляторів, про які ви знаєте, але поверніться до того, щоб подивитися на #define, вказаний у проекті / командному рядку / будь-якому випадку на нерозпізнаних компіляторах.
Стів Джессоп

4
Як ваше певне рішення для VisualStudio допоможе у питанні OP щодо перехресної платформи ??
alex tingle

3
@Jon: Хм. Вони НЕ підтримуються в будь-якому вигляді міжплатформного середовища за визначенням . За винятком випадків, коли MS визначає крос-платформу - працює над новими смаками Windows.
EFraim

1
@EFraim: Так, ви можете ЦІЛИТИ 32- або 64-розрядні за допомогою VS, але це не те, про що я говорю. Загальні конфігурації проектів та назви, які я їм присвоюю, абсолютно не мають нічого спільного з платформою. Якщо конфігурації проекту стосуються VS, то це шкода, оскільки вони дуже зручні.
Джон Сейгель

1
Я думаю, що це правильна відповідь. Це надійніше, ніж намагатися повторно виявити речі. Усі ІДЕ, які я коли-небудь бачив, підтримують цю функцію в якійсь формі, і я сумнуюсь, що ті, кого я ніколи не бачив, підтримують її також. Якщо ви використовуєте make або jam, ви можете встановити змінні з командного рядка, коли викликається, звичайним чином.

1

Я розміщую 32-бітні та 64-бітні джерела в різних файлах, а потім вибираю відповідні вихідні файли за допомогою системи збирання.


2
Це буде схоже на те, що система збирання дає вам такий прапор, як -DBUILD_64BIT. Часто деякі речі дуже схожі як на 32, так і на 64-бітні, тому наявність цього файлу в одному файлі може бути досить практичним.
Алексіс Вілке

Підтримка вихідних файлів-близнюків схильна до помилок. IMO навіть величезний #if bit64 .. весь код, для 64bit #else .. весь код, для 32bit #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-бітну (або іншу, з цього приводу):

версія.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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.