在指针运算中使用 char* 而不是 void*

Use of char* over void* in pointer arithmetic

我正在经历这个 code for learning purposes and have a question about this line:

return (char*)desc + sizeof *desc;

为什么 desc 转换为 char*? 我试着用我自己的代码模仿它:

#include <stdio.h>
#include <stdlib.h>

struct Test {
    int value;
};

int main() {

    struct Test* test = malloc(sizeof test);

    struct Test* test1 = (void*)test + sizeof *test;
    test1->value = 1;
    printf("%d\n", test1->value);

    struct Test* test2 = (void*)test1 + sizeof *test;
    test2->value = 10;
    printf("%d\n", test2->value);

}

这也行。但是,有什么区别呢?为什么使用 char*

注意:我使用 void* 只是为了看看它是否有效。由于 char* 与所讨论的 struct 无关,我只是想,“如果我在那边使用 void* 会怎么样?”。一个更具体的问题可能是,为什么不使用 int*float* 以及为什么使用 char*

为什么使用指向字符的指针

Why is desc cast to char*? … As char* has nothing to do with the struct in question…

在 C 语言中,除位域外的每个对象都由字节序列组成。1将对象的地址转换为 char * 会产生指向对象的第一个字节,您可以使用该指针访问对象的各个字节。

在标准 C 中,指针算法使用指向类型的单元。对于struct Test类型的指针pp+1指向p之后的下一个结构,p+2指向下一个结构,以此类推。对于char *类型的指针qq+1指向q之后的下一个charq+2指向q之后的char,依此类推上。

因此,要访问对象的各个字节,您可以将其地址转换为 char * 并使用它。

为什么不使用其他类型的指针

A more specific question could be, why not int* or float* & why char* is used?

使用

char * 是因为 C 中的所有对象(位域除外)都被定义为表示为字节序列。它们不一定是 intfloat 的序列。也可以使用 unsigned char *signed char *,由于符号问题的复杂性,unsigned char * 可能更可取。

C 标准有关于使用字符指针访问对象的特殊规则,因此它保证以这种方式访问​​对象的字节是有效的。相反,使用 int *float * 访问对象可能不起作用。允许编译器期望指向 int 的指针不会用于访问 float 对象,并且当它为程序生成机器指令时,它可能会根据该指令编写这些指令期待。使用字符指针可防止编译器假定 char * 与另一种指针不指向同一位置。

为什么指向 Void 的指针起作用

Note: I used void* just to see if that works.

要使指针算法起作用,编译器需要知道所指向对象的大小。当指向a的指针加1时struct Test,编译器需要知道将内部地址调整多少字节。

void 是一个不完整的类型。没有 void 个对象,void 没有大小。 (大小不为零。没有大小。)因此,当 pvoid *.

时,C 标准没有为 p+1 定义任何含义

然而,GCC defines arithmetic on void * as an extension。它的工作原理就好像 void 的大小为 1 个字节。 Clang 也支持这个。

由于这个扩展,使用 void * 指针进行算术运算与使用 char * 指针进行算术运算本质上是相同的。

这个扩展是不必要的;任何在 void * 上进行算术运算的代码都可以改写为使用 char *。有时这需要额外的转换来转换指针类型,这可能是将扩展添加到 GCC 的原因(以减少所需的代码量并使其看起来更好)。

您可以使用开关 -Werror -Wpointer-arith 禁用此扩展,或者您通常可以使用 -Werror -std=c18 -pedantic 请求更接近标准 C。我尽可能使用-Werror -std=c18 -pedantic,我推荐它。

脚注

1 位域是保存在一些更大的字节容器中的位序列,并且可能恰好与字节重合。