当前位置: 首页 > 知识库问答 >
问题:

严格的混淆现象规则真的是“双行道”吗?

池俊茂
2023-03-14

在这些注释中,user @Deduplicator坚持认为,如果别名指针或别名指针是指向字符的指针类型(限定或非限定,有符号或无符号< code>char *),则严格的别名规则允许通过不兼容的类型进行访问。所以,他的观点基本上是

long long foo;
char *p = (char *)&foo;
*p; // just in order to dereference 'p'

char foo[sizeof(long long)];
long long *p = (long long *)&foo[0];
*p; // just in order to dereference 'p'

符合并具有定义的行为。

然而,在我看来,只有第一种形式是有效的,即当别名指针是指向char的指针时;然而,在另一个方向上却不能,即当别名指针指向不兼容的类型(而不是字符类型)时,别名指针是一个< code>char *。

因此,上面的第二个片段会有未定义的行为。

这是怎么回事?这是正确的吗?为了记录在案,我已经阅读了这个问题和答案,并且接受的答案明确指出:

规则允许字符 * 的例外。始终假定 char * 别名其他类型。但是,这不会以另一种方式工作,因此无法假设您的结构别名为字符缓冲区。

(强调我的)

共有2个答案

乐健
2023-03-14

重复数据消除程序正确。允许编译器实现“严格别名”优化的未定义行为在使用字符值生成对象表示时不适用。

某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式,并且由不具有字符类型的左值表达式读取,则行为是未定义的。如果这种表示是由副作用产生的,该副作用通过不具有字符类型的左值表达式来修改对象的全部或任何部分,则该行为是未定义的。这种表示称为陷阱表示。

但是,您的第二个示例有未定义的行为,因为foo未初始化。如果您初始化foo,那么它只有实现定义的行为。这取决于long long的实现定义对齐要求,以及long long是否有任何实现定义的pad位。

请考虑是否将第二个示例更改为:

long long bar() {
    char *foo = malloc(sizeof(long long));
    char c;
    for(c = 0; c < sizeof(long long); c++)
        foo[c] = c;
    long long *p = (long long *) p;
    return *p;
}

现在对齐不再是问题,这个示例只依赖于<code>long long</code>的实现定义表示。返回的值取决于<code>long long</code>的表示形式,但如果该表示形式被定义为没有填充位,则该函数必须始终返回相同的值,并且必须始终是有效值。没有填充位,此函数无法生成陷阱表示,因此编译器无法对其执行任何严格的别名类型优化。

你必须非常努力地寻找一个符合标准的C实现,它在任何整数类型中都具有实现定义的pad位。我怀疑你会找到一个实现任何严格混淆现象类型优化的实现。换句话说,编译器不会使用访问陷阱表示引起的未定义行为来允许严格混淆现象优化,因为没有实现严格混淆现象优化的编译器定义了任何陷阱表示。

还要注意,如果< code>buf用全零(< code>'\0'字符)初始化,那么这个函数就不会有任何未定义的或实现定义的行为。整数类型的全比特零表示保证不是陷阱表示,并且保证具有值0。

现在,对于一个严格符合的示例,该示例使用 char 值来创建长整长整型值的保证有效(可能为非零)表示形式:

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

int
main(int argc, char **argv) {
    int i;
    long long l;
    char *buf;

    if (argc < 2) {
        return 1;
    }
    buf = malloc(sizeof l);
    if (buf == NULL) {
        return 1;
    }
    l = strtoll(argv[1], NULL, 10);
    for (i = 0; i < sizeof l; i++) {
        buf[i] = ((char *) &l)[i];
    }
    printf("%lld\n", *(long long *)buf);
    return 0;
}

此示例没有未定义的行为,并且不依赖于long的对齐或表示。这是为访问对象的字符类型异常创建的代码类型。特别是,这意味着标准C允许您在可移植的C代码中实现自己的memcpy函数

甘西岭
2023-03-14

你说这是无效的是正确的。正如您自己所引用的(所以我不会在这里重复引用),保证的有效类型转换只能从任何其他类型转换为char*。

另一种形式实际上是违反标准的,会导致不确定的行为。然而,作为一个小小的奖励,让我们讨论一下这个标准的背后。

在每个重要的体系结构上,Chars是唯一允许完全未对齐访问的类型,这是因为读取字节指令必须处理任何字节,否则它们将几乎无用。这意味着对字符的间接读取在我所知道的每个CPU上始终有效。

然而,另一种方法并不适用,除非指针在大多数拱形上对齐到8字节,否则无法读取uint64_t。

然而,有一个非常常见的编译器扩展,允许您将正确对齐的指针从char转换为其他类型并访问它们,但这是非标准的。还要注意,如果将指向任何类型的指针转换为指向char的指针,然后将其转换回,则结果指针将保证与原始对象相等。因此,这是可以的:

struct x *mystruct = MakeAMyStruct();
char * foo = (char *)mystruct;
struct x *mystruct2 = (struct mystruct *)foo;

mystruct2将等于mystruct。这也保证了该结构根据需要正确对齐。

因此,基本上,如果您想要指向char的指针和指向另一种类型的指针,请始终声明指向另一种类型的指针,然后转换为char。或者甚至更好地使用工会,这就是他们的基本目的......

但是,请注意,该规则有一个明显的例外。一些旧的马洛克实现曾经返回一个字符*。始终保证此指针可成功转换为任何类型,而不会破坏别名规则。

 类似资料:
  • 下面的getValue()成员函数是否违反了c严格别名规则? 根据该标准,我认为setValue()违反了严格的混淆现象,因为Double既不是聚合类型,也不是IEEE754_64的基类。 getValue()呢?当数据成员采用位字段形式时,它是否是一种未定义的行为,如下例所示? 我正在一个大型项目中使用类似的代码。GCC-O2和-O3输出错误值。如果我添加-fno严格的别名,问题就不存在了。此外

  • 这是一个典型的严格混叠违规示例: 但假设我们添加第二: 这段代码正确吗(不调用未定义的行为)? 标准[expr.reinterpret.cast]如下: 注意:将“指针到<code>T1</code>”类型的prvalue转换为“指针到 和<code>T2是对象类型,并且<code>T2的对齐要求不比<code>T 1 我们使用 类型的原始指针值来访问 类型。 当优化打开时,GCC和Clang都会

  • 我最近遇到了严格的别名规则,但我很难理解如何使用 在不违反规则的情况下执行类型双关。 我知道这违反了规定: 我知道我可以安全地使用C99中的联合进行打字双关语: 但是,我如何使用 在 C99 中安全地执行类型双关语?以下是否正确: 我怀疑代码仍然会违反严格的别名规则,因为变量< code>x地址处的内存可以被< code>x和一个解引用的< code>y修改。 如果通过< code>void *没

  • 让我们考虑以下(简化)代码来读取二进制文件的内容: 在我看来,下面一行: 包含未定义的行为:我们正在读取类型的成员,它覆盖在对象的数组之上,并且编译器可以自由地假设对象不别名。 问题是:我的解释正确吗?如果是,可以做什么来修复此代码?如果没有,为什么这里没有UB? 注1:我理解严格别名规则的目的(允许编译器避免不必要的内存加载)。此外,我知道在这种情况下,使用<code>std::memcpy</

  • 我不知道为什么下面的代码运行得很好,没有< code>gcc错误(< code >-f strict-aliasing-Wstrict-aliasing = 1 )。 如果我遵循严格的别名规则: n1570,§6.5表达式 对象的存储值只能由具有以下类型之一的左值表达式访问: -与对象的有效类型兼容的类型, — 与对象的有效类型兼容的类型的限定版本, -与对象的有效类型对应的有符号或无符号类型的类

  • 当违反严格的混淆现象规则时,我试图掌握未定义的行为。为了理解它,我读了很多关于SO的文章。然而,还有一个问题:我并不真正理解两种类型的非法别名。cpp-参考指出: 类型混淆现象 每当尝试通过AliasedType类型的glvalue读取或修改DynamicType类型对象的存储值时,除非以下之一为真,否则行为未定义: AliasedType和DynamicType类似 AliasedType是Dy