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

Posted

技术标签:

【中文标题】如何确定我平台上的最大指针大小?【英文标题】:How do I determine the largest pointer size on my platform? 【发布时间】:2019-04-18 12:16:30 【问题描述】:

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

我的问题:如何确定我的(编译目标)平台上指针的最大尺寸是多少?

注意:我的意思是 any 指针,包括指向类成员函数的指针;使用& 运算符可以获得的东西。我不是指“通俗地”称为指针的实体,即不是unique_ptrshared_ptr 等。

【问题讨论】:

你想在其中包含成员函数指针吗? 你是否也关心指向成员函数的指针void (Class::* func)(),以及指向成员数据的指针int Class::* mem 您是否包含智能指针对象作为指针? std::unique_ptrstd::shared_ptr 和破旧的std::auto_ptr 也许你应该告诉我们你真正想用这个最大化的指针大小做什么。 @MarekR: 不是直接的,但我以前认为通过将指针重新解释为void * 来对指针进行类型擦除是安全的,现在我知道这不是真的。所以我至少希望能够有一个std::array<std::byte, max_size_of_any_ptr> 来放入类型擦除的指针。 【参考方案1】:

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

[...] 除了指向静态成员的指针,引用“指针”的文本不适用于指向成员的指针。 […]

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

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

但是,该标准确实指定了对象指针 [expr.reinterpret.cast]/7 之间的相互转换、函数指针 [expr.reinterpret.cast]/6 之间的相互转换、数据成员指针 [expr.reinterpret.cast]/10.2 之间的相互转换以及成员函数指针 [expr.reinterpret.cast]/10.1 之间的相互转换。

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

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

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

例如使用

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 (*)()之间进行转换时可以存储任何数据成员指针。来自dummy_t dummy_t::*,并且在与void (dummy_t::*)()之间进行转换时可以存储任何成员函数指针。

play with it here

将其包装在一个类中,该类负责存储任何指针类型的任意值的所有转换(不要忘记处理可能的 cv 限定),应留给读者作为练习,主要是因为我今晚真的很想睡个好觉……

【讨论】:

感谢您让我知道aligned_union。并 +1 以获得出色的答案。【参考方案2】:

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

指向对象的指针 函数指针 成员函数指针

根据 C++17 标准 6.9.2.5,void * 保证足够大以容纳每个指向对象的指针:

指向 cv-qualified ([basic.type.qualifier]) 或 cv-unqualified 的指针 void 可用于指向未知类型的对象。这样的指针 应该能够保存任何对象指针。 cv void* 类型的对象 应具有与 cv 相同的表示和对齐要求 字符*。

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 说

除了指向静态成员的指针外,文本引用“指针” 不适用于指向成员的指针。

所以,最大可能的指针要么是void *,要么是函数指针:

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

【讨论】:

我认为这个问题值得引用标准 tbh @HolyBlackCat 啊,你是对的。我曾认为指向函数的指针不是严格意义上的“指针”,但我错了。标准参考:eel.is/c++draft/basic.compound#3 并非所有成员函数指针的大小都相同:***.com/questions/13875786/… @PeteBecker 我从未说过void * 大到足以容纳函数指针,仅用于数据指针(或指向对象的指针)。我应该将数据指针更改为指向对象的指针吗?问题中的链接也适用于 C,问题适用于 C++,我们不会在 C 中进行此讨论。 @MarekR 不,标准要求void* 可以容纳任何对象指针,因此,sizeof(void*) 是最大对象指针的大小。

以上是关于如何确定我平台上的最大指针大小?的主要内容,如果未能解决你的问题,请参考以下文章

不同平台下int类型指针类型的数据大小

函数指针的大小有啥保证?

如何确定 node.js 的正确“最大旧空间大小”?

空指针的大小

转换为指向运行时确定大小的数组的指针

确定多态 C++ 类的大小