你能否假设类型转换指针是安全的?

4

我听很多人说过,你不能保证类型转换不会有损失。这只有在你不知道你的处理器,也就是说,你没有验证数据类型使用的字节数时才会发生。让我举个例子:

如果你执行以下操作:

typedef struct
{
    int i;
    char c;
    float f;
    double d;
} structure;

size_t voidPtrSz = sizeof(void *);
size_t charPtrSz = sizeof(char *);
size_t intPtrSz = sizeof(char *);
size_t floatPtrSz = sizeof(float *);
size_t doublePtrSz = sizeof(double *);
size_t structPtrSz = sizeof(structure *);
size_t funcPtrSz = sizeof(int (*)(float, char));

printf("%lu\n", voidPtrSz);
printf("%lu\n", charPtrSz);
printf("%lu\n", intPtrSz);
printf("%lu\n", floatPtrSz);
printf("%lu\n", doublePtrSz);
printf("%lu\n", structPtrSz);
printf("%lu\n", funcPtrSz);

…输出结果如下…
4
4
4
4
4
4
4

你是否可以假设在所有情况下都可以安全地将一个特定数据类型的指针强制转换为另一种数据类型的指针?例如,如果你执行以下操作:

int foo(float, char)
{
}

void *bar(void)
{
    return (void *)foo;
}

int (*pFunc)(float, char) = bar();

你能确信地假设 pFunc 拥有 foo 的地址吗?

3
在所有现代平台上,指针的大小(在特定系统上)始终相同。可能存在不满足该条件的平台,尤其是对于函数指针。但是,您对函数指针的具体使用是安全的。 - Some programmer dude
2
阅读C99语言标准的6.3.2.3节,了解哪些转换是定义的,哪些是未定义的。(提示:您最后的代码示例会引发未定义的行为...) - Oliver Charlesworth
我相信如果你使用 typedef void (*vf_p)(); 并且将 bar 函数中的返回类型和强制转换从 void * 改为 vf_p,那么代码就会按照你的期望运行。 - Andrey Mishchenko
2
@JoachimPileborg:不是这样的。函数指针只能转换为另一个函数指针类型,而不能转换为void* - Fred Foo
1
OT: 要打印 size_t 类型,使用 "%zu" - alk
3个回答

6
关于你提供的代码示例,请参考C99语言标准6.3.2.3章节:
“指向void的指针可以被转换为或从任何不完整或对象类型的指针。任何不完整或对象类型的指针都可以被转换为指向void的指针,再转换回去;结果应该与原始指针相等。”
请注意,指向函数的指针与指向对象的指针是不同的。关于指向函数指针的转换,只有以下提到:
“一个类型为一种函数的指针可以转换为另一种函数的指针,然后再转换回来;结果应该与原始指针相等。如果将转换后的指针用于调用其类型与指向类型不兼容的函数,则行为是未定义的。”
因此,您的代码示例会导致未定义的行为。
如果我们避免使用函数指针转换,则下面的段落解释了一切:
“对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。如果所得指针未正确对齐指向的类型,则行为是未定义的。否则,再次转换时,结果应该与原始指针相等。”
请注意:在一般情况下,将指针类型进行转换并进行解引用是一个单独的问题(仅当您将其转换为char *,然后进行解引用才有效)。

2
你能假设在所有情况下,你可以安全地将特定的数据类型指针强制转换为另一种数据类型指针吗?
任何数据指针都可以安全地转换为`char*`或`void*`。因此创建的任何`char*`或`void*`都可以转换回其原始类型。在指针进行间接引用时,任何其他数据指针转换都会导致未定义的行为。
任何函数指针类型都可以转换为任何其他函数指针类型,但是不应通过错误的类型调用函数。将函数指针强制转换为`void*`或任何其他数据指针类型会导致未定义的行为。
那只有在你不知道处理器的情况下才是真的吗,也就是说,如果您还没有验证了您的数据类型所使用的字节数?
即使是这样,你也不是安全的。当C标准表示某个结构具有未定义的行为时,编译器编写者可以自由地处理该结构。结果是,即使您认为您知道具有UB的结构将被处理,因为您知道目标CPU,优化编译器也可能缩短时间并生成与您期望的完全不同的代码。

2
了解以下信息可能会有所帮助:如果您的目标是 POSIX 而不是 ISO C,则需要使用 void 指针来表示函数指针,以便实现 dlsym - tab

1

@Oli Charlesworth给了你一个很好的答案。

我希望我能为您解释一下指针是什么,以便您更好地理解指针机制:

指针是一个地址。这个地址是您数据的第一个字节的地址。指针的类型指定了从该第一个字节开始有多少个字节是数据,并且这些字节如何编码数据。

例如,在gcc x86上,如果您有一个int * p,则p所持有的值告诉您数据的起始地址,而p的类型(int *)告诉您在该地址处将会解释4个字节(按小端字节顺序)中的二进制补码表示的有符号数。

void *指针是“通用指针”。指针仍然持有一个地址,但指针类型不指定那里有什么类型的数据,甚至也不指定数据由多少字节组成,因此您永远无法通过void *指针访问数据,但正如之前答案所述,您可以安全地在指向任何不完整或对象类型的指针和指向void的指针之间进行转换。

函数指针保存函数的地址,指针的类型告诉您如何调用该函数(参数是什么类型和数量),以及函数返回什么。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接