【问题标题】:Can I do what I want with allocated memory我可以用分配的内存做我想做的事吗
【发布时间】:2017-01-26 11:26:15
【问题描述】:

我可以对分配的内存做些什么有限制吗?(标准)

例如

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

struct str{
    long long a;
    long b;
};

int main(void) 
{
    long *x = calloc(4,sizeof(long));
    x[0] = 2;
    x[3] = 7;
//is anything beyond here legal( if you would exclude possible illegal operations)
    long long *y = x; 
    printf("%lld\n",y[0]); 
    y[0] = 2;
    memset (x,0,16);
    struct str *bar = x;
    bar->b =  4;
    printf("%lld\n",bar->a); 
    return 0;
}

总结一下:

  • 只要大小合适,我可以将指针重新转换为其他数据类型和结构吗?
  • 那么,我可以先阅读再写作吗?
  • 如果不行,我写完还能看吗?
  • 我可以将它与小于分配内存的结构一起使用吗?

【问题讨论】:

    标签: c pointers malloc strict-aliasing


    【解决方案1】:

    y[0] 读取违反了严格的别名规则。您使用long long 类型的左值来读取有效类型long 的对象。

    假设您省略了该行;下一个麻烦的部分是memset(x,0,16);This answer 认为 memset 不会更新有效类型。标准不明确。

    假设memset 保持有效类型不变;下一期是bar-&gt;a的阅读。

    C 标准对此也不清楚。有人说bar-&gt;a 意味着(*bar).a,这是一个严格的别名违规,因为我们没有先将bar 对象写入该位置。

    其他人(包括我)说没问题:唯一用于访问的左值是bar-&gt;a;这是long long 类型的左值,它访问有效类型long long 的对象(y[0] = 2; 编写的对象)。

    有一个 C2X 工作组正在努力改进严格别名的规范以澄清这些问题。

    【讨论】:

    • 6.5p6 表示memove 和公司修改了有效类型。
    • @StoryTeller memmove 和 memcpy 当然可以,但我不认为 memset 属于同一个保护伞。如果你说 memset 设置了有效类型(到 char - 还有什么?),那么常见的习惯用法 memset(x, 0, n); 将一些整数初始化为零会导致 UB,所以我认为这不是一个实际的解释
    • 所以在bar-&gt;b 上进行读取或写入是可以的,因为它是同一个别名?
    • @MM:就我所见,C2X 提案似乎更侧重于添加更多编译器可以忽略潜在别名的情况,而不是增加程序员说嘿,这些事情可能别名。如果标准允许程序员说出什么东西可以别名并定义不同级别的别名兼容性,那么所有形式的别名都被标记的代码可以比现有规则更安全地优化,但程序员不需要使用-fno-strict-aliasing 让事情顺利进行。
    【解决方案2】:

    我可以将指针重新转换为其他数据类型,只要大小合适吗?

    您可以将1 重铸为最多与您分配的内存一样大的任何数据类型。但是,您必须写入一个值才能根据6.5p6更改所有涂层对象的有效类型

    那么,我可以在写作之前先阅读吗?
    如果不行,我写完还能看吗?

    没有。除非另有说明(calloc 是另一种情况)2,否则内存中的值是不确定的。它可能包含陷阱值。将一个值重新解释为另一种类型的强制转换是 UB,并且违反了严格别名 (6.5p7)

    我可以将它与小于分配内存的结构一起使用吗?

    是的,但那是一种浪费。


    1您需要先转换为void*。否则你会从编译器那里得到关于不兼容指针类型的正当投诉。
    2即使这样,某些类型也可能会陷入完全 0 位模式,所以这取决于。

    【讨论】:

    • 好的,第一个答案就完成了,因为它们依赖于这个操作。
    • @KamiKaze - 编辑以反映 M.M 对我对标准的生疏回忆的评论。
    【解决方案3】:

    大多数编译器都提供了一种模式,在这种模式下,指针的读取和写入将按照它们执行的顺序作用于底层存储,而不管所涉及的数据类型如何。该标准不要求编译器提供这种模式,但据我所知,所有优质编译器都提供这种模式。

    根据他们公布的理由,标准的作者在语言中添加了别名限制,目的是避免编译器在给定代码时做出悲观的别名假设:

    float f;
    float test(int *p)
    {
      f=1.0f;
      *p = 2;
      return f;
    }
    

    请注意,在基本原理中给出的示例中[非常类似于上面],即使通过指针 f 修改 f 使用的存储是合法的,查看代码的理性人也不会有理由认为这种事情很可能会发生。另一方面,许多编译器编写者认识到,如果给出如下内容:

    float f;
    float test(float *p)
    {
      f=1.0f;
      *(int*)p = 2;
      return f;
    }
    

    人们必须故意迟钝地认为代码不太可能修改float 使用的存储,因此没有理由认为高质量的编译器不应该将写入*(int*)p 视为可能会写信给float

    不幸的是,在随后的几年中,编译器编写者对基于类型的别名“优化”变得越来越激进,有时以明显且不可否认的方式超出标准允许的范围。除非程序永远不需要在不同时间以不同类型访问任何存储,否则我建议在支持它的编译器上使用-fno-strict-aliasing 选项。否则,您的代码可能符合标准并且现在可以工作,但在未来版本的编译器中失败,该版本的“优化”变得更加激进。

    PS--在某些情况下禁用基于类型的别名可能会影响代码的性能,但正确使用restrict-qualified 变量和参数应该避免悲观别名假设的成本。稍加注意,使用这些限定符将实现与激进别名相同的优化,但更安全。

    【讨论】:

    • 我想我必须做一些阅读才能完全理解你的答案。谢谢你的详细描述。
    • @KamiKaze:阅读restrict;即使不禁用基于类型的别名,它也可以提供性能优势,但如果这样做,它会变得尤为重要。
    猜你喜欢
    • 2011-02-27
    • 1970-01-01
    • 1970-01-01
    • 2014-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    相关资源
    最近更新 更多