【问题标题】:Can a char array be used with any data type?char 数组可以与任何数据类型一起使用吗?
【发布时间】:2016-11-25 10:04:09
【问题描述】:

malloc() 函数返回void* 类型的指针。它根据作为参数传递给它的size_t 值以字节为单位分配内存。结果分配是原始字节,可用于 C 中的任何数据类型(无需强制转换)。

在返回void * 的函数中声明的类型为char 的数组是否可以用于任何数据类型,例如malloc 的结果分配?

例如,

#include <stdio.h>

void *Stat_Mem();

int main(void)
{
    //size : 10 * sizeof(int)
    int buf[] = { 1,2,3,4,5,6,7,8,9,10 };

    int *p = Stat_Mem();

    memcpy(p, buf, sizeof(buf));

    for (int n = 0; n < 10; n++) {
        printf("%d ", p[n]);
    }
    putchar('\n');

    return 0;
}

void *Stat_Mem()
{
    static char Array[128];
    return Array;
}

【问题讨论】:

  • 没有。像 char array[n]; 这样的数组 具有 声明类型:字符数组。通过char(或其他字符类型)以外的类型的左值访问它是未定义的。 [m/c/re]alloc() 的特殊之处在于它们分配内存没有声明的类型。
  • char* 可以为任何类型设置别名。但反之亦然。基本上,您在这里尝试以int* 的身份访问char*,这违反了此规则。
  • @machine_1 静态字符数组为转换类型正确对齐是无效的。
  • 您可以将任何指针返回为void *。但是你只能用它的有效类型来取消引用它!
  • @machine_1:C11 标准草案 n1570:6.5 表达式 6 访问其存储值的对象的有效类型是对象的声明类型(如果有)。 87) 如果通过具有非字符类型类型的左值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及后续访问不修改存储的值。

标签: c arrays types c11


【解决方案1】:

如果阅读 C89 标准的基本原理,那么存在类型别名规则的唯一原因是避免要求编译器做出“最坏情况别名假设”。给定的示例是:

    int a;
    void f( double * b )
    {
        a = 1;
        *b = 2.0;
        g(a);
    }

如果程序在联合中创建了一个“char”数组,其中包含适合任何类型的对齐方式,获取其地址,并且除非通过结果指针,否则从不访问该结构的存储,那么应该没有理由别名规则应该会造成任何困难。

值得注意的是,该标准的作者认识到一个实现可以同时兼容但无用;请参阅 C89 2.2.4.1 的基本原理:

虽然有缺陷的实现可能会设计出满足此要求的程序,但仍会成功地无用,但委员会认为,这种独创性可能需要更多的工作,而不是使某些东西有用。委员会的意思是,实施者不应将转换限制解释为硬连线参数的值,而应将其解释为一组判断实施的标准。

虽然该特定声明是针对实现限制做出的,但将 C89 解释为与之前的 C 方言远程兼容的唯一方法是将其视为更广泛的应用:标准不尝试详尽地指定程序应该能够做的所有事情,但依赖于编译器编写者的一些常识。

使用字符类型数组作为任何类型的后备存储,假设一个确保对齐问题得到处理,不应该对非笨拙编写的编译器造成任何问题。该标准没有强制编译器编写者允许这样的事情,因为他们认为没有理由期望他们这样做。不幸的是,他们未能预见到该语言在 21 世纪将走向何方。

【讨论】:

  • 该引文完全断章取义。实现限制的问题甚至与通过对象有效类型以外的类型的左值访问对象无关。
  • @EOF:证明标准的作者确实打算全面描述 质量 C 实现的行为,并认识到 C 实现可以同时完全兼容然而质量如此差,毫无用处。允许兼容的 C 编译器忽略明显存在别名的情况这一事实并不意味着这样做的 C 编译器不应被认为比识别此类别名的 C 编译器差。
  • s/确实打算/没有打算/
【解决方案2】:

不,您不能将任意字节数组用于任意类型,因为可能存在对齐问题。该标准在 6.3.2.3 转换/指针(强调我的)中说:

指向对象或不完整类型的指针可以转换为指向不同类型的指针 对象或不完整类型。 如果结果指针未正确对齐 指向类型,行为未定义。否则,当再次转换回来时, 结果应与原始指针比较。

作为 char 作为最小对齐要求,您无法确保您的 char 数组对于任何其他类型都正确对齐。这就是为什么 malloc 保证 malloc 获得的缓冲区(即使它是 void *)具有最大可能的对齐要求以能够接受任何其他类型。


我认为

union {
    char buf[128];
    long long i;
    void * p;
    long double f;
};

应该为任何类型正确对齐,因为它与最大的基本类型(如 6.2.5 类型中定义)兼容。我很确定它适用于所有常见的实现(gcc、clang、msvc,...),但不幸的是,我找不到任何标准允许它的确认。本质上是因为 6.5 表达式 §7 中定义的严格别名规则:

一个对象的存储值只能由具有以下之一的左值表达式访问 以下类型:

  • 与对象的有效类型兼容的类型,
  • 与对象的有效类型兼容的类型的限定版本,
  • 对应于有效类型的有符号或无符号类型 对象,
  • 一种类型,它是有符号或无符号类型,对应于 对象的有效类型,
  • 一种聚合或联合类型,其中包括上述类型之一 成员(递归地包括子聚合或包含联合的成员),或
  • 一种字符类型。

所以恕我直言,没有可移植且符合标准的方法来构建不使用malloc 的自定义分配器

【讨论】:

  • 这里的对齐方式不相关。这是因为如果对齐方式相同,这仍然是未定义的。问题是静态char数组的有效类型,不能改为int。见这里:stackoverflow.com/a/38510909/4082723
  • @2501:该链接指向已删除的答案,并非所有人都能看到。
  • @2501:对齐在 ARM 架构上是可见的:如果不尊重它,就会出错,即使是非语言律师也能理解。我知道将指向一种类型的指针转​​换为指向另一种类型的指针会给出一个只能转换回原始类型的指针。但更难让它真实
  • 最后关于union的部分是有问题的。我在标准中发现的唯一看似相关的东西是 C11 草案标准 n1570 6.2.5 类型 28 指向 void 的指针应具有与指向字符类型的指针相同的表示和对齐要求。 [...]所有指向结构[和分别]联合类型的指针应具有彼此相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求。 ,但这是在讨论指针本身的对齐,而不是指向的类型.
  • 我同意,对齐很重要,但由于 C 允许所有类型的对齐相同,最终归结为严格的别名。
【解决方案3】:

静态对象Array的声明类型为char。这个对象的有效类型是它的声明类型。静态对象的有效类型无法更改,因此对于程序的其余部分,Array 的有效类型为char

如果您尝试访问类型不兼容或不在此列表中的对象的值1,则行为未定义。

您的代码尝试使用int 类型访问Array 的存储值。此类型与char 类型不兼容且不在异常列表中,因此当您使用int 指针p 读取数组时,行为未定义:

printf("%d ", p[n]);

1(引自:ISO:IEC 9899:201X 6.5 表达式 7)
对象的存储值只能由左值访问 具有以下类型之一的表达式:
- 一种 兼容对象的有效类型,
——合格的 与对象的有效类型兼容的类型的版本,
— 一种类型,它是有符号或无符号类型,对应于 对象的有效类型,
— 有符号或无符号的类型 type 对应于有效类型的限定版本 对象,
— 聚合或联合类型,包括以下之一 其成员之间的上述类型(包括,递归地, 子聚合或包含联合的成员),或
— 一种字符类型。

【讨论】:

  • memcpy(p, buf, sizeof(buf)); 之后取消引用p 是否有任何事情合法?
  • @EugeneSh。仅当您使用分配的存储持续时间时。
  • 不确定我是否理解这句话。
  • @EugeneSh。在标准中查找存储期限
  • 出于好奇,您能想出什么方法可以让代码利用 c11 的新 _Alignas 限定符而不依赖于标准没有要求的行为吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-13
  • 1970-01-01
  • 1970-01-01
  • 2020-04-11
  • 1970-01-01
  • 1970-01-01
  • 2012-07-25
相关资源
最近更新 更多