【问题标题】:What does it mean that void* has the same representation and memory alignment as char*?void* 与 char* 具有相同的表示和内存对齐是什么意思?
【发布时间】:2021-04-15 12:01:13
【问题描述】:

我一直在阅读一些关于 void* 类型指针的文章,并从标准中找到了这个要求。

6.2.5.27:

指向 void 的指针应具有与指向字符类型的指针相同的表示和对齐要求。39) 同样,指向兼容类型的合格或非合格版本的指针应具有相同的表示和对齐要求。

我看到标准不保证所有指针类型都具有相同的长度,所以这里的底线是void* 指针具有与char* 相同的长度和对齐规则,对吧?

我没有得到的是脚注 39),它说

相同的表示和对齐要求意味着作为函数的参数、函数的返回值和联合成员的可互换性。

我的问题是:

  1. “可互换性”是什么意思?是不是说函数void* Func(void*)的参数和返回值都可以是char*

  2. 如果是,是否是编译器进行的隐式转换?

  3. 工会成员是怎么回事?我真的不明白这句话的意思。谁能给我一个简单的例子?

【问题讨论】:

  • 不详述,意思是指针就是指针就是指针。在 C 中,任何类型的指针都可以分配给 void* 并返回而无需强制转换。这是可互换性的基础。不会发生隐式转换,void* 指针只是一个没有特定类型的指针。而且,由于类型控制指针运算,您不能对void* 指针进行指针运算。就像您不能取消引用 void* 指针一样,因为缺少类型信息——这将是一个不完整的类型。所以你必须在引用之前分配或转换一个 void 指针。
  • @DavidC.Rankin 这些都是真实的陈述,但我不认为它们是引用的段落的内容。这是关于 void*char* 具有相同的表示。 void* 不必具有与其他指针类型相同的表示或对齐方式。
  • 我认为这意味着它们的大小相同并且具有相同的对齐要求。 (这也与严格的别名规则-类型兼容和char* 例外相吻合)我没有对标准部分进行批判性解释,只是更实际地讨论了该部分如何不包含任何隐藏的陷阱。跨度>
  • 我通常认为这是一个标准,即这些可互换类型是“兼容”的,目的是满足函数调用要求,但放弃将其转化为规范的官方语言。例如,您可以在一个翻译单元中定义void *foo(void *p),并在另一个翻译单元中声明char *foo(char *p),然后使用后者调用它,因为类型是可互换的,所以它会起作用。但根据 C 标准的规范文本,它是未定义的,除了 C 2018 6.2.5 28 中的这段关于相同的表示。
  • 该问题将段落引用为 6.2.5.27,我认为这意味着第 6.2.5 条第 27 段(您不应使用该格式,因为它无法区分第 6.5 条第 1 段和第 6.5.1 条) ,但我在任何官方版本的C标准中都没有找到6.2.5 27。 1999 年是第 26 段。2011 年是第 28 段(虽然我在看 2011 年的草稿,但我认为这是发布前的最后一个)。

标签: c language-lawyer void-pointers


【解决方案1】:

在 C 中,任何数据指针都可以传递给期望 void * 的函数,并且 void * 可以存储到任何指针类型。 void * 和其他指针类型之间存在隐式转换。但这并不意味着这种转换是无害的。在void *int * 具有不同表示形式的某些架构上,从int * 转换为void * 然后再转换回int * 被指定为产生相同的指针值,但反过来不成立: void *int * 并返回到 void * 可能会产生不同的值,特别是如果 void * 不是通过转换 int * 获得的。

可互换性意味着这种隐式转换不会改变指针的表示。两种方式都可以成功地进行转换:将字符指针转换为void * 并返回会产生相同的指针,反之亦然。

这是一个例子:

#include <assert.h>
#include <stdio.h>
#include <string.h>

int main() {
    char *s = "abc";
    char *s1;
    void *p;
    void *p1;

    assert(sizeof(p) == sizeof(s));
    memcpy(&p, &s, sizeof(p));
    p1 = s;
    assert(p == p1);
    memcpy(&s1, &p1, sizeof(s1));
    assert(s == s1);
    return 0;
}

但是请注意,这并不意味着!memcmp(&amp;p1, &amp;s, sizeof(p1)),因为指针可能有填充位。您也不能通过void * 强制转换来违反严格的别名规则:

  • float f = 1.0; unsigned int i = *(int *)(void *)&amp;f' 不正确。
  • float f = 1.0; unsigned int i; memcpy(&amp;i, &amp;f, sizeof(i)); 如果 sizeof(int) == sizeof(float) 则正确,但可能会产生陷阱值。

【讨论】:

    【解决方案2】:

    指针只是内存中的一个地址。您可以认为内存是一个字节的连续区域,非常大(例如,在 32 位进程上它将是 4 GB,但通常该进程无法使用全部取决于系统)。

    这意味着指针的值实际上是一个整数,表示内存中字节的从零开始的索引(例如,值为0 的指针指的是内存中的第一个字节,但实际上你将无法取消引用这个地址,因为它是一个空指针)。

    当您取消引用指针时,它所做的是读取/写入该地址。读/写的大小取决于指针的类型。如果一个指针是int并且它在那个系统上的大小是32位,也就是4个字节;它将从该地址开始读/写 4 个字节。对齐的意思是值如何存储在内存中。假设存储在内存中的值需要 16 字节对齐,这意味着它的起始地址必须乘以 16。

    我这里解释的只是一个高级的指针,应该足够入门了。实际上它有很多相关的东西,比如内存保护、分页等等。

    【讨论】:

    • 这是一个简单的内存模型,现在很常见。 OP 引用的 C 标准中的语言解决了不同指针类型可能不具有相同表示或大小的非平凡情况。这样的架构可能不符合 POSIX 标准,因为对于 POSIX,所有指针都必须具有相同的大小,但仍然存在很长时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 2020-05-01
    • 2016-06-11
    • 2011-07-07
    相关资源
    最近更新 更多