需要帮助解决警告:取消引用类型双关指针将打破严格的别名规则

Need help to resolve warning: dereferencing type-punned pointer will break strict-aliasing rules

我正在研究一组C代码来优化它。我在修复损坏的代码时遇到警告。

环境是 Linux,C99,使用 -Wall -O2 标志编译。

最初结构文本定义如下:

    struct text {
        char count[2];
        char head[5];
        char textdata[5];
    }

代码指向 return 指针 T1T2 指向预期的 headtextdata 字符串:

int main(void) {
    struct text *T1;
    char *T2;
    char data[] = "02abcdeabcde";

    T1 = (struct text *)data;
    T2 = T1->textdata;
    gettextptr((char *)T1, T2);
    printf("\nT1 = %s\nT2 = %s\n", (char *)T1, T2);
    return (0);
}

void gettextptr(char *T1, char *T2) {
    struct text *p;
    int count;

    p = (struct text *)T1;
    count = (p->count[0] - '0') * 10 + (p->count[1] - '0');

    while (count--) {
        if (memcmp(T2, T1, 2) == 0) {
            T1 += 2;
            T2 += 2;
        }
    }
}

这没有按预期工作。预计 return 第一个 'c' 和最后一个 'e' 的地址。通过GDB,我发现,从gettextptr()到父函数的执行指针return,并没有保留T1T2的地址。然后我尝试使用双指针 'Call by reference' 的另一种方法:

int main(void) {
    struct text *T1;
    char *T2;
    char data[] = "02abcdeabcde";

    T1 = (struct text *)data;
    T2 = T1->textdata;
    gettextptr((char **)&T1, &T2);
    printf("\nT1 = %s\nT2 = %s\n", (char *)T1, T2);
    return (0);
}

void gettextptr(char **T1, char **T2) {
    struct text *p;
    int count;

    p = (struct text *)(*T1);
    count = (p->count[0] - '0') * 10 + (p->count[1] - '0');

    while (count--) {
        if (memcmp(*T2, *T1, 2) == 0) {
            *T1 += 2;
            *T2 += 2;
        }
    }
}

当我用 -Wall -O2 编译此代码时,我收到以下 GCC 警告:

 pointer.c: In function ‘main’:
 pointer.c:23: warning: dereferencing type-punned pointer will break strict-aliasing rules

所以:

  1. 第一种情况下的代码是按值调用的吗?

  2. 在保持严格的别名规则的同时,是否允许 (char **) 进行转换?

  3. 我缺少什么来解决这个警告?

严格的别名规则是 paragraph 6.5/7 of the Standard。它基本上说您只能通过兼容类型的左值访问对象,可能还有其他限定符;相应的有符号/无符号类型;数组、结构或联合类型及其成员之一,或字符类型。您收到的诊断表明您的代码违反了该规则,而且确实违反了多次。

你很早就让自己陷入困境:

    T1 = (struct text *)data;

这种转换是允许的,虽然不能保证生成的指针正确对齐,但是在不违反严格的别名规则的情况下,您可以用 T1 做很多事情。特别是,如果您使用 *-> 取消引用它——这实际上是您接下来要做的事情——那么您访问一个 char 数组就好像它是一个 struct text。这是不允许的,但反过来就是另一回事了。

T1 转换为 char * 并通过该指针访问指向的数组,就像您稍后所做的那样,是您 可能 做吧。

gettextexpr() 相同(两个版本)。它执行上述相同类型的转换,并在访问 p->count 时取消引用转换后的指针。由此产生的行为违反了严格的别名规则,因此是未定义的。然而,在第二种情况下,GCC 实际上抱怨的可能是访问 *T1 就好像它是 char *,而实际上它是 struct text * —— 另一个独立的严格别名违规。

因此,针对您的具体问题:

  1. Was the code calling by value in first case?

C 按值传递,所以是的。在第一种情况下,您按值传递了两个 char 指针,然后您可以使用它们来修改调用者的 char 数据。在第二种情况下,您按值传递两个 char * 指针,您可以并且确实使用它来修改调用者的 char * 变量。

  1. Isn't (char **) permitted for casting while keeping strict aliasing rules?

不,绝对不是。将 转换为 char *(而非 char **)可以让您通过结果指针访问对象的表示,因为取消引用 char * 会产生左值字符类型,但没有任何类型可以在没有严格别名含义的情况下从 一般地从 转换。

  1. What am I missing to resolve this warning?

您没有意识到您正在尝试做的事情 根本上 是不允许的。 C 不允许像访问 struct text 一样访问 char 数组。编译器仍然可以接受这样做的代码,但它的行为是未定义的。

通过放弃强制转换为结构的方法来解决警告,无论如何,该方法仅提供语法糖粉。去掉所有的强制转换写起来其实更简单明了:

    count = ((*T1)[0] - '0') * 10 + ((*T1)[1] - '0');

摆脱所有转换使用可能更清楚 sscanf:

    sscanf(*T1, "%2d", &count);

另请注意,即使允许,您的特定访问模式似乎也对结构成员的布局做出了语言不合理的假设。实现可以在成员之间和最后一个成员之后使用任意填充,而您的代码无法适应这种情况。