将函数指针转换为 uintptr_t / intptr_t 无效吗?

Is conversion of a function pointer to a uintptr_t / intptr_t invalid?

Microsoft extensions to C and C++:

To perform the same cast and also maintain ANSI compatibility, you can cast the function pointer to a uintptr_t before you cast it to a data pointer:

int ( * pfunc ) ();
int *pdata;
pdata = ( int * ) (uintptr_t) pfunc;

C 的基本原理,修订版 5.10,2003 年 4 月:

Even with an explicit cast, it is invalid to convert a function pointer to an object pointer or a pointer to void, or vice versa.

C11:

7.20.1.4 Integer types capable of holding object pointers

是否意味着pdata = ( int * ) (uintptr_t) pfunc;无效?

作为史蒂夫·萨米特 :

The C standard is written to assume that pointers to different object types, and especially pointers to function as opposed to object types, might have different representations.

虽然pdata = ( int * ) pfunc;会导致UB,但似乎pdata = ( int * ) (uintptr_t) pfunc;会导致IB。这是因为“Any pointer type may be converted to an integer type”和“An integer may be converted to any pointer type”和uintptr_t是整数类型。

根据定义

int (*pfunc)();
int *pdata;

,作业

pdata = (int *)pfunc;
pdata = (int *)(uintptr_t)pfunc;

是,IMO,等效。在数据指针大小与函数指针大小相同或大于函数指针的平台上,两种赋值都将按需要工作。但是在数据指针比函数指针的平台上,两次赋值都会不可避免地刮掉函数指针的一些位,导致数据指针无法转换回后面的原始函数指针。

特别是,我认为这两个赋值是等价的,尽管在第二个赋值中存在 (uintptr_t) 转换。我相信 cast 完全没有完成任何事情。

在数据指针小于函数指针且类型uintptr_t与数据指针大小相同的平台上,在赋值

pdata = (int *)(uintptr_t)pfunc;

,转换为 (uintptr_t) 会刮掉 pfunc 的一些值。

在数据指针小于函数指针且类型uintptr_t与函数指针大小相同的平台上,在赋值

pdata = (int *)(uintptr_t)pfunc;

,转换为 (int *) 会刮掉 pfunc 的一些值。

在这两种情况下,pdata 最终只会得到 pfunc 原始值的一小部分。

(这里我忽略了带有填充位等的架构的可能性。在一些奇怪的假设平台上,函数指针大于数据指针,但额外的位总是 0,这两个分配将再次工作。)

(我也忽略了 int *void * 大小不同的可能性。我不确定这是否会影响答案,是否通过 [=尝试从 int (*)() 转换为 int * 时,24=] 或多或少是不必要的或必需的。)

转换为 uintptr_t 仅在定义了此类型时有效,在使用古老编译器的遗留系统上可能并非如此。但是请注意,uintptr_t 必须对任何对象指针都足够大,尤其是 char *void *,但可能比函数指针小。这种架构在今天很少见,Microsoft 编译器可能不再支持它们,但它们在 16-bit world(MS/DOS、Windows 1、2 和 3.x)中很常见,其中中型模型有 32 位分段代码指针和 16 位数据指针。

另请注意,C 标准允许 int *void * 具有不同的大小和表示形式,尽管没有 Microsoft 编译器支持此类外来目标。

在具有现代编译器的当前系统上,所有数据指针和代码指针几乎总是具有相同的大小和表示形式。这实际上是 POSIX 兼容性的要求,因此使用中间转换到 (uintptr_t) 的建议是有效的。

为了完全的可移植性,如果目标是通过不透明的 void * 传递函数指针,您始终可以分配适当函数指针类型的对象,用 pfunc 初始化它并传递它的地址:

// setting up the void *
int (*pfunc)();
void *pdata = malloc(sizeof pfunc);
memcpy(pdata, &pfunc, sizeof pfunc);

// using the void *
int (**ppfunc)() = pdata;
(*ppfunc)();     // equivalent to (**ppfunc)();

Is conversion of a function pointer to a uintptr_t / intptr_t invalid?

没有。它可能有效。可能是未定义的行为


函数指针到 ìnt* 的转换未定义。也不是任何 object 指针。也不 void *.
pdata = ( int * ) pfunc; 未定义的行为

允许将函数指针转换为整数类型,但有限制:

Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type. C17dr 6.3.2.3 6

也允许指针类型的整数。

An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation. C17dr 6.3.2.3 6

void * 到整数到 void * 被定义。 对象指针to/fromvoid*已定义。那么 optional (u)intptr_t 类型足以 round-trip 成功。然而我们关心的是一个函数指针。通常足够的函数指针比 int *.

更宽

因此将函数指针转换为 int * 只有通过整数类型才有意义,越宽越好。

VS 可能会通过 可选 类型 uintptr_t 进行推荐,并且在其他平台上 如果信息无损 可能就足够了。然而 uintmax_t 可能会减少信息损失,尤其是在指向整数步骤的函数指针中,所以我迂腐地建议:

pdata = ( int * ) (uintmax_t) pfunc;

无论采取何种步骤,代码都可能成为特定于实现的代码,值得保护。

#ifdef this && that
  pdata = ( int * ) (uintmax_t) pfunc;
#else
  #error TBD code
#endif

将解决方案从问题迁移到答案:

这是微软的回答:

Q: How exactly "cast the function pointer to a uintptr_t before you cast it to a data pointer" leads to maintaining "ANSI compatibility"?

A: Without the cast to uintptr_t it’s possible that the code will fail to compile with other compilers, even if they use the same pointer model. For example: https://gcc.godbolt.org/z/9EjTe1s4x - if you add the uintptr_t it compiles without warnings/errors.