2.4 检测处理器体系结构

NOTE:此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-02/recipe-04 中找到,包含一个C++示例。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。

19世纪70年代,出现的64位整数运算和本世纪初出现的用于个人计算机的64位寻址,扩大了内存寻址范围,开发商投入了大量资源来移植为32位体系结构硬编码,以支持64位寻址。许多博客文章,如 https://www.viva64.com/en/a/0004/ ,致力于讨论将C++代码移植到64位平台中的典型问题和解决方案。虽然,避免显式硬编码的方式非常明智,但需要在使用CMake配置的代码中适应硬编码限制。本示例中,我们会来讨论检测主机处理器体系结构的选项。

准备工作

我们以下面的arch-dependent.cpp代码为例:

  1. #include <cstdlib>
  2. #include <iostream>
  3. #include <string>
  4. #define STRINGIFY(x) #x
  5. #define TOSTRING(x) STRINGIFY(x)
  6. std::string say_hello()
  7. {
  8. std::string arch_info(TOSTRING(ARCHITECTURE));
  9. arch_info += std::string(" architecture. ");
  10. #ifdef IS_32_BIT_ARCH
  11. return arch_info + std::string("Compiled on a 32 bit host processor.");
  12. #elif IS_64_BIT_ARCH
  13. return arch_info + std::string("Compiled on a 64 bit host processor.");
  14. #else
  15. return arch_info + std::string("Neither 32 nor 64 bit, puzzling ...");
  16. #endif
  17. }
  18. int main()
  19. {
  20. std::cout << say_hello() << std::endl;
  21. return EXIT_SUCCESS;
  22. }

具体实施

CMakeLists.txt文件中,我们需要以下内容:

  1. 首先,定义可执行文件及其源文件依赖关系:

    1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. project(recipe-04 LANGUAGES CXX)
    3. add_executable(arch-dependent arch-dependent.cpp)
  2. 检查空指针类型的大小。CMake的CMAKE_SIZEOF_VOID_P变量会告诉我们CPU是32位还是64位。我们通过状态消息让用户知道检测到的大小,并设置预处理器定义:

    1. if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    2. target_compile_definitions(arch-dependent PUBLIC "IS_64_BIT_ARCH")
    3. message(STATUS "Target is 64 bits")
    4. else()
    5. target_compile_definitions(arch-dependent PUBLIC "IS_32_BIT_ARCH")
    6. message(STATUS "Target is 32 bits")
    7. endif()
  3. 通过定义以下目标编译定义,让预处理器了解主机处理器架构,同时在配置过程中打印状态消息:

    1. if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i386")
    2. message(STATUS "i386 architecture detected")
    3. elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i686")
    4. message(STATUS "i686 architecture detected")
    5. elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64")
    6. message(STATUS "x86_64 architecture detected")
    7. else()
    8. message(STATUS "host processor architecture is unknown")
    9. endif()
    10. target_compile_definitions(arch-dependent
    11. PUBLIC "ARCHITECTURE=${CMAKE_HOST_SYSTEM_PROCESSOR}"
    12. )
  4. 配置项目,并注意状态消息(打印出的信息可能会发生变化):

    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4. ...
    5. -- Target is 64 bits
    6. -- x86_64 architecture detected
    7. ...
  5. 最后,构建并执行代码(实际输出将取决于处理器架构):

    1. $ cmake --build .
    2. $ ./arch-dependent
    3. x86_64 architecture. Compiled on a 64 bit host processor.

工作原理

CMake定义了CMAKE_HOST_SYSTEM_PROCESSOR变量,以包含当前运行的处理器的名称。可以设置为“i386”、“i686”、“x86_64”、“AMD64”等等,当然,这取决于当前的CPU。CMAKE_SIZEOF_VOID_P为void指针的大小。我们可以在CMake配置时进行查询,以便修改目标或目标编译定义。可以基于检测到的主机处理器体系结构,使用预处理器定义,确定需要编译的分支源代码。正如在前面的示例中所讨论的,编写新代码时应该避免这种依赖,但在处理遗留代码或交叉编译时,这种依赖是有用的,交叉编译会在第13章进行讨论。

NOTE:使用CMAKE_SIZEOF_VOID_P是检查当前CPU是否具有32位或64位架构的唯一“真正”可移植的方法。

更多信息

除了CMAKE_HOST_SYSTEM_PROCESSOR, CMake还定义了CMAKE_SYSTEM_PROCESSOR变量。前者包含当前运行的CPU在CMake的名称,而后者将包含当前正在为其构建的CPU的名称。这是一个细微的差别,在交叉编译时起着非常重要的作用。我们将在第13章,看到更多关于交叉编译的内容。另一种让CMake检测主机处理器体系结构,是使用CC++中定义的符号,结合CMake的try_run函数,尝试构建执行的源代码(见第5.8节)分支的预处理符号。这将返回已定义错误码,这些错误可以在CMake端捕获(此策略的灵感来自 https://github.com/axr/cmake/blob/master/targetarch.cmake ):

  1. #if defined(__i386) || defined(__i386__) || defined(_M_IX86)
  2. #error cmake_arch i386
  3. #elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64)
  4. #error cmake_arch x86_64
  5. #endif

这种策略也是检测目标处理器体系结构的推荐策略,因为CMake似乎没有提供可移植的内在解决方案。另一种选择,将只使用CMake,完全不使用预处理器,代价是为每种情况设置不同的源文件,然后使用target_source命令将其设置为可执行目标arch-dependent依赖的源文件:

  1. add_executable(arch-dependent "")
  2. if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i386")
  3. message(STATUS "i386 architecture detected")
  4. target_sources(arch-dependent
  5. PRIVATE
  6. arch-dependent-i386.cpp
  7. )
  8. elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i686")
  9. message(STATUS "i686 architecture detected")
  10. target_sources(arch-dependent
  11. PRIVATE
  12. arch-dependent-i686.cpp
  13. )
  14. elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64")
  15. message(STATUS "x86_64 architecture detected")
  16. target_sources(arch-dependent
  17. PRIVATE
  18. arch-dependent-x86_64.cpp
  19. )
  20. else()
  21. message(STATUS "host processor architecture is unknown")
  22. endif()

这种方法,显然需要对现有项目进行更多的工作,因为源文件需要分离。此外,不同源文件之间的代码复制肯定也会成为问题。