【问题标题】:Does this macro violate the STRICT ALIASING RULE?此宏是否违反严格别名规则?
【发布时间】:2016-08-17 10:06:22
【问题描述】:

我正在修复一些不是我写的代码,所以我发现了这个:

#define get_u_int16_t(X,O)  (*(u_int16_t *)(((u_int8_t *)X) + O))

如果违反了规则,我该如何更改它以保持规则?

宏是这样调用的:

if(get_u_int16_t(packet->payload, i) == ...) { ... }

其中 payloadconst unsigned char *iunsigned int

情况是:

struct orig {
   [...]
   struct pkt packet;
}*;

struct pkt {
   [...]
   const u_int8_t *payload;
 }*;

这样调用:

struct orig * flow;
struct pkt * packet = &flow->packet;

有效载荷是一个字符串

i0 的值开头,它位于有效载荷长度的循环 for 内(u_int16_t len):

for(i = 0; i < len; i++) {
   if(get_u_int16_t(packet->payload, a) == /*value*/) {
   // do stuff
}

【问题讨论】:

  • 从您显示的代码中无法确定。出示 SSCCE。
  • 这是否违反严格的别名规则取决于为X 传递的参数类型。如果参数是u_int16_t*类型并且指向一个有效类型为u_int16_t的对象,并且它的值可以转换为u_int8_t*u_int8_t没有比u_int16_t更严格的对齐要求),行为是好的-定义。
  • 我也在这里问过,所以这就是我打开这个问题的原因,只是想更好地思考plus.google.com/u/0/+MicheleCampus/posts/YjUFgDQbDy8?cfem=1
  • 我将修改添加一段代码的示例。
  • 即使您最近添加了内容,仍然不足以提供明确的答案。 packet-&gt;payload 的类型是 const unsigned char *,当然,但它实际上指向的是什么类型的对象? (暂时忘记整个指针转换问题,因为大多数编译器将指针类型转换视为返回指向同一对象的指针)。更具体地说:packet-&gt;payload 指向的对象的有效类型(根据 6.5p7)是什么?

标签: c macros strict-aliasing type-punning


【解决方案1】:

宏本身不违反严格的别名规则;这取决于你如何使用它。如果您只使用它来读取类型为u_int16_t 或兼容类型的现有对象,那么它很好;另一方面,如果您使用它来阅读例如64 位整数或浮点对象的一部分,那么这将是一个严格的别名违规,以及(可能)一个对齐违规。

与往常一样,您可以使用memcpy 使代码安全:

inline u_int16_t read_u_int16_t(const void *p) {
    u_int16_t val;
    memcpy(&val, p, sizeof(val));
    return val;
}
#define get_u_int16_t(X,O)  (read_u_int16_t(((u_int8_t *)X) + O))

正如@2501 指出的,如果u_int8_t 不是字符类型,这可能是无效的,所以你应该只使用char * 进行指针运算:

#define get_u_int16_t(X,O)  (read_u_int16_t(((char *)X) + O))

【讨论】:

  • 由于我们不知道 u_int8_t 是否是字符类型,因此可能由于指向不兼容类型的指针之间的转换而导致行为未定义。
  • @EOF 对齐无关;如果u_int8_t 不是字符类型(charsigned charunsigned char,则如果X 不是兼容指针,则强制转换是非法的。确实,目前没有非字符的 8 位整数类型,但有一个 8 位非字符类型来表示 UTF-8 数据的现有提议,它可能像枚举一样疯狂。
  • @ecatmur:C11 标准草案 n1570:6.3 转换 6.3.2.3 指针 7 指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未正确对齐 68) 引用类型,则行为未定义。否则,当再次转换回来时,结果将等于原始指针。
  • 我不知道 "u_int8" 是什么,但是不将 uint8_t 视为字符类型的编译器对于现实世界的应用程序毫无用处 - C 标准该死。我非常怀疑现实世界中是否存在这种无用的编译器。
  • @2501,不,你首先是对的,尽管推理很困难。 EOF引用的那段并没有说只在一个方向上转换的结果是什么,也就是说,通常允许将某个指针转换为另一种指针类型,但是没有关于它指向什么的规定(如果有的话) .所需要的只是它在转换回 为原始类型时指向原始对象。如果使用此宏,可能会取消引用指向未知位置的指针。
【解决方案2】:

有两种方法可以编写您感兴趣的类型的代码:

  1. 使用“unsigned char*”类型的指针来访问任何东西,从多个字节中组装值,并容忍性能损失。

  2. 如果您知道指针对齐不会成为问题,请使用不会在语义上破坏它的 C 方言。如果使用-fno-strict-aliasing 调用,GCC 可以实现这样的方言,但由于 gcc 无法在不阻止有用且合理的优化的情况下阻止钝的“优化”,因此从这样的方言中获得良好的性能可能需要学习如何使用 restrict

有些人会认为方法 #1 更好,对于许多 PC 端编程目的,我倾向于同意。然而,对于嵌入式软件,我建议远离 gcc 的默认方言,因为不能保证 gcc 今天认为定义的行为明天仍然如此。我不确定 gcc 的作者从哪里得到的概念,即标准的作者试图指定质量微处理器实现应该支持的所有形式的别名,而不是为没有类型的平台建立最低基线“unsigned char”以外的双关语会很有用。

【讨论】:

  • 感谢您的评论。所以在你看来,答案是正确的,我必须按照建议使用 char * 而不是 u_int8_t ?
  • @Kyrol:在严格阅读规则的情况下,不能保证uint8_t* 将被识别为具有与unsigned char* 相同的别名保证。不过,我认为更重要的一点是选择 C ​​方言的重要性。如果只针对具有已知存储布局规则的实现,程序员可以通过利用这些规则(禁用别名规则)获得的优势通常会超过编译器可以通过别名规则获得而无法通过restrict 获得的优势。
  • @Kyrol:还要注意,在许多嵌入式平台上,memcpy 对于小动作来说绝对是可怕的。用unsigned char 组装东西通常比使用uint16_t* 慢两倍以上; memcpy 的速度损失可能接近 10 倍。
  • 哇,我不知道memcpy的那个问题。实际上我认为指针对齐可能是一个问题:我正在阅读这个stackoverflow.com/questions/227897/…
  • 我不明白restrict 有什么用处,因为我的指针指向同一个位置。谢谢你帮我解开这些疑惑
猜你喜欢
  • 2020-04-11
  • 2016-02-24
  • 1970-01-01
  • 2017-02-25
  • 2016-10-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多