将 void * 函数参数从类型转换为类型不一致的方法

Methods to convert `void *` function parmeter inconsistant from type to type

注意:这个问题试图改进我试图提出的问题here,但没有达到要求。
另外,我看到了, and this。他们讨论相似的概念,但不回答这些问题。

我的环境是 Windows 10,为了测试我使用了两个编译器,CLANG and GCC.

我通过 void * 函数参数传递变量,需要转换它们。我想获得一些关于我在不同类型的方法之间看到的不一致的反馈。

以下是测试函数的精简描述,该测试函数使用 void * 参数和枚举值参数来指示传入的类型,以适应多种输入类型。:

void func(void *a, int type)
{
    switch(type) {
        case CHAR://char
            char cVar1    = (char)a;      //compiles with no warnings/errors, seems to work
            char cVar2    = *(char *)a;   //compiles with no warnings/errors, seems to work
            break;
        case INT://int
            int iVar1     = (int)a;       //compiles with no warnings/errors, seems to work
            int iVar2     = *(int *)a;    //compiles with no warnings/errors, seems to work
            break;
        case FLT://float
            float fVar1   = (float)a;      //compile error:  (a1)(b1)
            float fVar2   = *(float *)a;   //requires this method
         case DBL://double
            double dVar1  = (double)a;     //compile error: (a1)(b1)(b2)
            double dVar2  = *(double *)a;//this appears to be correct approach
            break;
    };
}  

调用方式:

int main(void)
{

    char   c = 'P';
    int    d = 1024;
    float  e = 14.5;
    double f = 0.0000012341;
    double g = 0.0001234567;

    void *pG = &g;

    func(&c, CHAR);//CHAR defined in enumeration, typical
    func(&d, INT);
    func(&e, FLT);
    func(&f, DBL);
    func(pG, DBL);

    return 0;
}

上面评论中与标志相关的确切错误文本如下:

CLANG - 版本 3.3

gcc - (tdm-1) 5.1.0

供下面讨论参考

我的结果表明转换 floatdouble 需要方法 2。
但是对于 charint 方法 2 似乎是可选的,即方法 1 编译得很好,并且似乎始终如一地工作。

问题:

C standard 明确允许指针和整数类型之间的转换。这在有关指针转换的第 6.3.2.3 节中有详细说明:

5 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.

6 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.

假设您在将整数类型传递给函数时将其转换为 void *,然后将其转换回正确的整数类型,这可以完成 提供 实现允许它。特别是 GCC 将允许这样做,假设所讨论的整数类型至少与 void *.

一样大

这就是转换适用于 charint 情况的原因,但是您需要传递值(转换为 void *)而不是地址。

例如,如果您这样调用函数:

func4((void *)123, INT);

那么函数可以这样做:

int val = (int)a;

val 将包含值 123。但是如果您这样称呼它:

int x = 123;
func4(&x, INT);

那么函数中的val会把mainx的地址转换为整数值。

根据第 6.5.4p4 节关于强制转换运算符的规定,明确不允许在指针类型和浮点类型之间进行强制转换:

A pointer type shall not be converted to any floating type. A floating type shall not be converted to any pointer type.

当然,通过 void * 传递值最安全的方法是将值存储在适当类型的变量中,传递其地址,然后将函数中的 void * 转换回正确的指针类型。这保证有效。

在您的调用站点,您正在传递每个变量的 地址

func4(&c, CHAR);
func4(&d, INT);
func4(&e, FLT);
func4(&f, DBL);
func4(pG, DBL);

(这是正确的做法。)因此,在 func4 中,您 必须 使用您所描述的 "method 2":

T var1    = (T)a;    // WRONG, for any scalar type T
T var2    = *(T *)a; // CORRECT, for any scalar type T

您只遇到浮点类型 T 的编译时错误,因为 C 标准明确允许从指针转换为整数类型。但是这些转换产生的值与作为参数提供的变量的 地址 有某种 [实现定义的] 关系,而不是它的 .例如,

#include <stdio.h>
int main(void)
{
    char c = 'P';
    printf("%d %d\n", c, (char)&c);
    return 0;
}

是一个打印两个数字的有效程序。除非您 运行 在 IBM 大型机上使用,否则第一个数字将为 80。第二个数字是不可预测的。也有可能是80,但如果是,那就是意外,不能靠什么。每次您 运行 程序时,它甚至可能不是相同的数字。

我不知道你所说的“[方法 1] 似乎有效”是什么意思,但如果你实际上得到了与你传入的相同的值,那纯属偶然。方法二是你应该做的。

It would seem that recovering a value from a void * function argument should always require method 2, so why does method 1 (seem to) work with char and int types? Is this undefined behavior?

因为C特别允许整数和指针之间的转换。这是允许的,因为可能需要将绝对地址表示为整数,特别是在与硬件相关的编程中。结果可能很好,也可能会调用未定义的行为,请参阅下面的详细信息。

当您需要在指针和整数之间进行转换时,您应该始终使用 uintptr_t 来代替定义明确且可移植的转换。这种类型最初不是 C 的一部分,这就是为什么仍然允许转换为其他整数类型的原因。

If method 1 works for char and int, why does it not also work with at least the float type? It's not because their sizes are different, i.e.: sizeof(float) == sizeof(int) == sizeof(int *) == sizeof(float *). Is it because of a strict aliasing violation?

因为浮点类型不像整数类型那样有允许转换的特殊情况。他们宁愿有一个明确的规则禁止从指针转换为浮点数。因为做这样的转换没有任何意义。

严格别名仅适用于对存储的值执行 "lvalue access" 时。例如,您只能在此处执行此操作:*(double *)a。您通过与对象的有效类型(也 double)兼容的类型(double)访问数据,所以这很好。

然而,

(double *)a 永远不会访问实际数据,而只是试图将指针类型转换为其他类型。所以严格的别名不适用。

通常,C 允许大量的野指针转换,但只有当您开始通过不正确的类型实际取消引用数据时,您才会遇到麻烦。然后您就可以 运行 解决类型不匹配、未对齐和严格别名等问题。


详情:

  • char c = 'P'; ... char cVar1 = (char)a;.
    从指针类型到整数类型的转换。结果未定义或实现定义 1)。没有发生指向数据的左值访问,严格的别名不适用 2).
  • char c = 'P'; ... char cVar2 = *(char *)a;.
    通过字符指针对字符进行左值访问。完美定义 3)
  • int d = 1024; ... int iVar1 = (int)a;.
    从指针类型到整数类型的转换。结果未定义或实现定义 1)。没有发生指向数据的左值访问,严格别名不适用 2).

  • int d = 1024; ... int iVar2 = *(int *)a;
    通过 int 指针访问 int 的左值。完美定义 3).

  • float e = 14.5; ... float fVar1 = (float)a;.
    从指针类型到浮点数的转换。不兼容的类型转换,转换运算符约束违反 4).

  • float e = 14.5; ... float fVar2 = *(float *)a;.
    float 通过 float 指针的左值访问。完美定义 3).

  • double...同上float


1)C17 6.3.2.3/6:

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.

2) C17 6.5 §6 和 §7。参见 What is the strict aliasing rule?

3) C17 6.3.2.1 左值、数组和函数指示符,以及
C17 6.3.2.3/1:

A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

此外,type 非常适合通过指向 typeC17 6.5/7 的(合格)指针进行左值访问:"a type compatible with the effective type of the object".

4) 不是 C17 6.3.2.3 中列出的有效指针转换之一。违反约束 C17 6.5.4/4:

A pointer type shall not be converted to any floating type. A floating type shall not be converted to any pointer type.