为什么在将 void 指针转换为指向 char 指针的指针后取消引用会产生问题?

Why dereferencing after casting void pointer to pointer to char pointer makes problem?

我学会了指针到指针。 我对将“整数指针”(char*)(int) 和“指向字符指针的指针的空指针”强制转换后的取消引用感到好奇。 (char**)(void*)

现在当我想取消对指向 char 指针的 void 指针的引用时出现一些问题。(char**)(void*)

我试了2个案例

指向指针的整数(char*)(int)

#include <stdio.h>
int main()

{
    
    int a = 10;
    int b;
    void* ptr = &a;

    b = ptr;
    printf("%d\n", *(unsigned int*)b);
    printf("%d\n", *(void**)(b));
    printf("%p\n", *(char**)(b));

    return 0;
}

*(unsigned int*)b 
*(void**)(b)
*(char**)(b)**

这三个读到地址10

但是下面的代码是问题。 (请收看ptr_speacial) “指向 char 指针的指针的空指针。” (char**)(void*)

#include <stdio.h>
int main()
{
    
    int arr[5] = { 1,2,3,4,5 };
    char arr2[5] = { 1,2,3,4,5 };
    

    void* ptr_arr;
    void* ptr_arr2;

    void* ptr_special;

    ptr_arr = arr;
    ptr_arr2 = arr2;

    ptr_special = (char*)ptr_arr2 + 4;


    printf("address : %p: value : %d\n", ptr_special, *(char**)ptr_special);
    printf("%d\n", *(char**)ptr_arr);

    return 0;
}

*(char**)ptr_special 没有读取地址中的 5,而是 -858993659

我不确定,但我认为这是溢出问题。在这个案例中发生了什么?请帮助我!

printf("address : %p: value : %d\n", ptr_special, *(char**)ptr_special);

您将 char* 作为参数传递,因此程序将从 ptr_special.

中获取 8 个字节(或 4 个,取决于编译器设置)

arr2开始的记忆会是这样的:

01 02 03 04 05 cc cc cc cc cc cc cc cc cc cc cc

它可能与许多原因(优化设置等)不同,但重要的是 arr2 之后有一些值。当您使用表达式 *(char**)ptr_special 时,程序会将其解释为 0xcccccccccccccc05(或 0xcccccc05)并传递给 printf 函数。

因为您使用 %d 作为格式说明符,函数会将值打印为 4 字节整数 0xcccccc05,即 -858993659.

看看这个:

#include <stdio.h>
int main()
{
    char arr2[] = { 1,2,3,4,5 };
    char arr3[] = { 1,2,3,4,5 };
    
    void* ptr_special2 = arr2 + 4;
    void* ptr_special3 = arr3 + 4;

    printf("address : %p: value : %d\n", ptr_special2, *(char**)ptr_special2);
    printf("address : %p: value : %d\n", ptr_special2, *(char**)ptr_special3);

    return 0;
}

输出(可能不同):

address : 0x7ffc5841755a: value : 50462981
address : 0x7ffc5841755a: value : 1074892805

现在试试这个:

    char arr2[] = { 1,2,3,4,5,0,0,0 };
    char arr3[] = { 1,2,3,4,5,1,0,0 };

输出:

address : 0x7fffecb291fc: value : 5
address : 0x7fffecb291fc: value : 261

在阅读...之前先弄清楚发生了什么...

解释:

在我的系统上,“指向字符指针的指针”是 4 个字节长。所以 - 当你在 arr2 中获取 5 的地址,将其转换为 char**,并取消引用它时 - 系统读取 4 个字节,从 5 开始。

arr2中,这4个字节是5 0 0 0,在小端系统中被解释为5。

arr3中,这4个字节是5 1 0 0,在小端系统中被解释为5 + 256 = 261。

在您的代码中,5 之后的三个字节是未知的。你不知道编译器放在那里的是什么(如果有的话)。您正在读取超出数组末尾的内容,因为您正试图从该地址处只有 1 个有效字节的数组中读取 4 个字节(或系统上的任何 sizeof(char**))。

这是未定义的行为,这意味着标准几乎允许任何事情发生 - 但通常情况下,您可以预期:

  1. “随机垃圾”字节
  2. 下一个局部变量的开始
  3. 访问冲突

首先,C 在涉及各种指针和整数类型之间的转换时有相当宽松的规则。如果您添加显式转换并因此强制进行转换,则在大多数情况下,代码将 compile。但是,这并不一定意味着代码是正确的。有多种形式的未定义行为与不同类型之间的这种疯狂转换相关联。对齐、type/address 大小、兼容类型、指针别名 (What is the strict aliasing rule?) 等等。有很多潜在的错误需要考虑。

此代码存在许多 问题。具体来说:

  • b = ptr; 这是无效的 C。如果没有显式转换,您不能将整数分配给指针。 . Yes "it compiles" but not without warnings. Code can compile with warnings and still be invalid C, see What must a C compiler do when it finds an error?.

  • b = (int)ptr; 仍然会有问题,因为指针类型可能不适合 int。对于这种情况,您应该使用 uintptr_t 而不是 int。由于使用了错误的类型,当我在我的计算机上 运行 它们时,您的示例会出错并且不会打印地址,而是一些乱码,例如 0022FE400000000A 它已经设法弄乱了指针地址+ 相同 64 位访问中堆栈中的值。不能保证 - 这是未定义的行为 - 这只是一台特定计算机上发生的事情。

  • printf("%d\n", *(unsigned int*)b); 有效但有问题,因为它将实际数据的符号从 int 更改为 unsigned int,然后立即告诉 printf 转换使用 %d 说明符回到 int。没有任何意义。

  • 通常,您应该 printf 指针 %p 否则任何事情都可能发生。

  • *(void**)(b)这么复杂,为什么不写成(void*)b.

  • *(char**)(b) 是有问题的,因为它将 a 的第一个字节重新解释为可能已签名的 char 。您应该改用 uint8_t。更有问题的是,代码声明 b 的内容(即:假定 &a)是一个 char**,然后您将其转换为 char*。这是一个无效的指针转换,因为 char*char** 都不是与 int* 兼容的类型。编译器可以在这里自由地做疯狂的事情。

  • *(char**)ptr_special 只是使用了错误的指针类型。您从数组转换为 char*void*。然后声称 void* 实际上是一个 char** 并尝试取消引用它,所以你会得到乱码。正确的代码是 *(char*)ptr_special.

总结:

不要不要在不相关的类型之间进行野指针转换,除非您完全确定自己在做什么。编译器可能会允许所有形式的废话通过,但这并不意味着代码是正确的。