【问题标题】:Unexpected behaviour of a function which returns a void pointer返回 void 指针的函数的意外行为
【发布时间】:2021-10-10 00:42:37
【问题描述】:

我写了一个空指针函数来求整数的平方。


void * square(const void * num);

int main(){

    int x = 6;
    void * ptr = &x;
    printf("%d", square(ptr));

}

void * square(const void * num){
    return (*(int * )num) * (*(int *)num);
}

据我所知,这不应该起作用,因为函数应该返回一个指向(*(int * )num) * (*(int *)num) 结果的空指针,而不是结果本身。但是,代码仍然有效。有人请解释一下。

【问题讨论】:

  • 编译时可能会出现警告?任何理智的编译器都应该警告您返回类型与函数签名不匹配。
  • 指针是数字。您正在返回一个数字,因此它被隐式转换回void*,然后在您的printf 中它被隐式转换为int
  • "代码仍然有效" - 这是未定义行为的一种可能结果,这就是您的程序所具有的。
  • 你尝试这个有什么特别的原因吗?通常,square 函数将接受 int 并返回 int,或者接受 double 并返回 double。您是否只是想了解 void 指针?一个更现实的例子可能更有启发性。

标签: c void-pointers


【解决方案1】:

在函数return 语句的表达式中:

(*(int * )num) * (*(int *)num);

num 是一个void *,其中包含int 的地址。然后这个指针被正确地转换回int *:

(int * )num

并取消引用:

(*(int * )num)

给我们存储在x 中的值,特别是6。这在这个表达式中发生了两次给我们6 * 6,即36。这是return 语句使用的值。由于函数的返回类型是void *int 的值 36 以实现定义的方式转换为 void *(在大多数实现中,这将是一个值为 36 的指针)。

然后,square 类型为 void * 的结果将传递给使用 %d 格式说明符的 printf。由于此格式说明符需要int,但它却被赋予void *,这将触发undefined behavior。这基本上意味着无法保证程序会做什么。

未定义的行为表现出来的一种方式是,事情似乎可以正常工作,这就是您所看到的。现在让我们看看可能发生了什么。

在具有平面内存模型的系统上,指针只是一个数字,通常大小为 4 或 8 个字节。因此,在这种情况下,指针可能会像传递整数类型一样传递给函数。假设您的系统是 little-endian,即最低有效字节在前,则传递给函数的前 4 个字节将是最低位字节。所以假设一个指针是 8 个字节,十六进制值 0x24 0x0 0x0 0x0 0x0 0x0 0x0 0x0 将被压入堆栈。然后,printf 将读取这些字节的前 4 个作为整数,从而打印出36

重申一下,不能保证此代码会打印出正确的结果。由于实施细节,它恰好是那样。

【讨论】:

    【解决方案2】:

    首先,在c中将整数隐式转换为指针是非法的。

        return (*(int * )num) * (*(int *)num);
    

    您的编译器至少应该对此给出警告。如果你的编译器没有给你任何警告,我认为必须有一些编译器标志让它警告你。

    但是,许多 C 编译器不会在遇到此类情况时停止编译。他们警告你,然后按照你这样写的方式编译它:

    return (void *)(((int * )num) * (*(int *)num));
    

    您希望square() 必须返回指向计算结果的 void 指针,但将整数转换为 void 指针并不意味着您获得指向该整数的指针。这意味着您的编译器会将您的该整数的位数组解释为 void 指针。如果该整数为 9,则生成的 void 指针将指向地址 9。

    这不是非法的,但是当您实际尝试访问 void 指针指向的位置时,这具有未定义的行为。未定义的行为意味着当你在其他编译器上编译它时,你会得到不同的结果。

    另外,printf("%d", square(ptr)) 会导致未定义的行为,因为您指定将int 提供给您的第二个参数,但您没有。这也可能导致任何事情取决于您的编译器和计算机,但在这种情况下,您的编译器只是将您的 void 指针的位数组解释为整数。

    如果你想返回指向结果的 void 指针,你应该这样做:

    void *square(const void *num)
    {
        int *ret;
    
        ret = malloc(sizeof(int));
        *ret = (*(int *)num) * (*(int *)num);
        return ret;
    }
    

    如果您想了解更多关于什么是未定义行为,可以从here 找到 c 标准文档。当您想知道某些代码 sn-p 的工作原理时,依赖编译器的工作并不是一个好习惯。

    【讨论】:

      猜你喜欢
      • 2020-06-11
      • 1970-01-01
      • 2012-02-26
      • 1970-01-01
      • 2021-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-16
      相关资源
      最近更新 更多