如何确定平台上的最大指针大小?

How do I determine the largest pointer size on my platform?

在(C 和)C++ 中,指向不同类型的指针 don't necessarily have the same size。我本来希望 void * 一定是最大的,但似乎连这个都不能保证。

我的问题:如何确定我的(编译目标)平台上指针的最大大小?

注意:我指的是任意指针,包括指向class成员函数的指针;使用 & 运算符可以获得的东西。我不是指被称为指针的 "colloquially" 实体,即不是 unique_ptrshared_ptr 之类的实体。

有 3 种不同类型的指针,它们可以有不同的大小:

  • 指向对象的指针
  • 函数指针
  • 成员函数指针
根据 C++17 标准 6.9.2.5,

A void * 保证足够大以容纳每个指向对象的指针:

A pointer to cv-qualified ([basic.type.qualifier]) or cv-unqualified void can be used to point to objects of unknown type. Such a pointer shall be able to hold any object pointer. An object of type cv void* shall have the same representation and alignment requirements as cv char*.

class A;

typedef void (A::*a_func_ptr)(void);
typedef void (*func_ptr)(void);

size_t a = sizeof(a_func_ptr), b = sizeof(func_ptr), c = sizeof(void*);

std::cout << std::max(a, std::max(b, c)) << std::endl;

应该完成这项工作。

编辑:C++17 标准 6.9.2.3 说

Except for pointers to static members, text referring to “pointers” does not apply to pointers to members.

因此,最大可能的指针是 void * 或函数指针:

std::cout << std::max(sizeof(void*), sizeof(void(*)(void))) << std::endl;

C++语言中有四种完全不相关的class指针类型:对象指针、函数指针、非静态数据成员指针和非静态成员函数指针。术语"pointer"一般只适用于对象和函数指针类型[basic.compound]/3:

[…] Except for pointers to static members, text referring to “pointers” does not apply to pointers to members. […]

指针和指向非静态成员的指针实际上完全被视为两种完全不同的复合类型[basic.compound]/1(这是有道理的,因为非静态成员指针更像是相对偏移量而不是实际地址).

除了对象和函数指针之间有条件支持的转换,其语义(如果完全支持的话)将由实现定义[expr.reinterpret.cast]/8,没有办法在这四个之间进行转换classes 的指针类型。

但是,该标准确实指定了对象指针之间的相互转换性 [expr.reinterpret.cast]/7, interconvertibility amongst function pointers [expr.reinterpret.cast]/6, interconvertiblity amongst data member pointers [expr.reinterpret.cast]/10.2, and interconvertibility amongst member function pointers [expr.reinterpret.cast]/10.1

因此,虽然没有所有其他指针类型通常都相关的通用指针类型,但将任何对象指针转换为某个任意对象指针类型并返回是明确定义的行为。将任何函数指针转换为某种任意函数指针类型并返回是明确定义的行为。将任何数据成员指针强制转换为某个任意数据成员指针类型并返回是明确定义的行为。将任何成员函数指针强制转换为某个任意成员函数指针类型并返回是明确定义的行为。所有这些不同的 classes 指针类型的一个共同点是它们都是对象类型 [basic.types]/8.

虽然这并不能严格保证,例如,所有成员函数指针类型的大小都相同,但它确实隐含地确定了某些成员函数指针类型的任何对象都可以有效地用于存储任何成员函数指针值。可能仍然存在比其他成员函数指针类型更大的成员函数指针类型,但它们不可能比其他成员函数指针类型包含更多信息,因为标准要求与任何其他成员函数指针类型之间的转换不能丢失信息(原始值始终可以恢复).相同的论点类似地适用于所有其他 classes 指针类型。

基于所有这些,我认为在标准 C++ 中找到 "the largest pointer type" 在技术上是不可能的。然而,虽然在技术上可能无法找到最大的指针类型本身,但基于上述论点,绝对有可能找到可靠存储任何指针类型值所需的存储量上限。虽然这两个在技术上是不同的东西,但实际上,第二个很可能几乎和第一个一样好(没有合理的编译器会随机添加大量填充位到某些指针类型的值表示,只是因为这样做在技术上是合法的).至少我很难想象除了存储指针值之外,您可能还想对您所要求的信息进行处理。

使用,例如

using generic_obj_ptr = void*;
using generic_fun_ptr = void (*)();

class dummy_t;
using generic_dat_mem_ptr = dummy_t dummy_t::*;
using generic_mem_fun_ptr = void (dummy_t::*)();

你可以计算

auto obj_ptr_size = sizeof(generic_obj_ptr_t);
auto fun_ptr_size = sizeof(generic_fun_ptr_t);
auto dat_mem_ptr_size = sizeof(generic_dat_mem_ptr_t);
auto mem_fun_size = sizeof(generic_mem_fun_ptr_t);

auto max_ptr_size = std::max({ sizeof(generic_obj_ptr_t), sizeof(generic_fun_ptr_t), sizeof(generic_dat_mem_ptr_t), sizeof(generic_mem_fun_ptr_t) });
auto max_ptr_align = std::max({ alignof(generic_obj_ptr_t), alignof(generic_fun_ptr_t), alignof(generic_dat_mem_ptr_t), alignof(generic_mem_fun_ptr_t) });

或者直接使用

using ptr_storage_t = std::aligned_union<0U, generic_obj_ptr_t, generic_fun_ptr_t, generic_dat_mem_ptr_t, generic_mem_fun_ptr_t>;

甚至

using any_ptr_t = std::variant<generic_obj_ptr_t, generic_fun_ptr_t, generic_dat_mem_ptr_t, generic_mem_fun_ptr_t>;

或纯形式:

using any_ptr_t = std::variant<void*, void (*)(), dummy_t dummy_t::*, void (dummy_t::*)()>;

当转换为void*和从void*转换时可以存储任何对象指针值的存储,当转换为void (*)()和从void (*)()转换时可以存储任何函数指针值,任何数据成员指针都可以在转换为 dummy_t dummy_t::* 和转换为 dummy_t dummy_t::* 时存储,并且在转换为 void (dummy_t::*)() 和转换为 void (dummy_t::*)() 时可以存储任何成员函数指针。

play with it here

将其包装在一个 class 中的任务,该任务负责所有转换以存储任何指针类型的任意值(不要忘记处理可能的 cv 限定),应保留为为reader做运动,主要是今晚很想睡个好觉…