C语言 将"void *"函数参数在类型之间不一致的转换方法



注意:这个问题试图改进我在这里试图问的问题,但失败了。
另外,我看到了这个,还有这个。 他们讨论类似的概念,但没有回答这些问题。

我的环境是Windows 10,为了测试,我使用了两个编译器,CLANG和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

  • (a1) - ...错误:指针无法强制转换为类型"float">

海湾合作委员会 -(TDM-1) 5.1.0

  • (b1) - ...错误:指针值用于预期浮点值的位置
  • (b2) - ...错误:指针无法强制转换为类型"double">

供以下讨论参考

  • 方法 1 ==type var = (type)val;
  • 方法 2 ==type var = *(type *)val;

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

问题:

  • 似乎从void *函数中恢复值 参数应该总是需要方法 2,那么为什么方法 1(似乎 以) 使用charint类型? 这是未定义的行为吗?

  • 如果方法 1 适用于charint,为什么它至少不适用于float类型? 这不是因为它们的大小不同,即:sizeof(float) == sizeof(int) == sizeof(int *) == sizeof(float *). 是因为严格的别名冲突吗?

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

5整数可以转换为任何指针类型。除非前面指定,否则结果是实现定义的,可能不会 正确对齐,可能不指向引用的实体 类型,并且可能是陷阱表示形式。

6任何指针类型都可以转换为整数类型。除非前面指定,否则结果是实现定义的。如果 结果不能用整数类型表示,行为是 定义。结果不必在任何值的范围内 整数类型。

假设在将整数类型传递给函数时将其强制转换为void *,然后将其转换回正确的整数类型,只要实现允许,就可以执行此操作。 特别是GCC将允许这样做,假设所讨论的整数类型至少与void *一样大。

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

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

func4((void *)123, INT);

然后该函数可以这样做:

int val = (int)a;

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

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

然后,函数中的val将包含转换为整数值main中的x地址。

根据有关强制转换运算符的第 6.5.4p4 节,明确禁止在指针类型和浮点类型之间进行强制转换:

指针类型不得转换为任何浮动类型。 浮动类型不得转换为任何指针类型。

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

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

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

(这是正确的做法。 因此,在func4中,您必须使用您所描述的"方法 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 %dn", c, (char)&c);
return 0;
}

是打印两个数字的有效程序。 第一个数字将是 80,除非您在 IBM 大型机上运行。 第二个数字是不可预测的。 也可能是80岁,但如果是,那是一个意外,不是可以依赖的。 每次运行程序时,它甚至可能不是相同的数字。

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

似乎从 void * 函数参数中恢复值应该始终需要方法 2,那么为什么方法 1(似乎)适用于 char 和 int 类型呢?这是未定义的行为吗?

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

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

如果方法 1 适用于 char 和 int,为什么它至少不适用于浮点类型?这不是因为它们的大小不同,即:sizeof(float) == sizeof(int) == sizeof(int *) == sizeof(float *)。是因为严格的别名冲突吗?

因为浮点类型没有像整数类型那样允许转换的特殊情况。相反,它们有一个明确的规则,禁止从指针到浮点进行转换。因为进行此类转换没有任何意义。

严格别名仅在对存储的值执行"左值访问"时适用。例如,您只在此处执行此操作:*(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;
    通过指针访问intint值。完美定义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

任何指针类型都可以转换为整数类型。除非前面指定, 结果是实现定义的。如果结果无法以整数类型表示, 行为未定义。结果不必在任何整数的值范围内 类型。

2) C17 6.5 §6 和 §7。请参阅什么是严格的别名规则?

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

指向

void 的指针可以转换为指向任何对象类型的指针,也可以从指向任何对象类型的指针转换。指向的指针 任何对象类型都可以转换为指向 void 的指针,然后再返回;结果应 与原始指针相等。

此外,type完全可以通过指向type的(限定)指针进行 lvalue 访问,C17 6.5/7:"与对象的有效类型兼容的类型"。

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

指针类型不得转换为任何浮动类型。浮动型不得 转换为任何指针类型。

最新更新