繁体   English   中英

Clang 13 -O2 产生奇怪的输出,而 gcc 没有

[英]Clang 13 -O2 produces weird output while gcc does not

有人可以向我解释为什么使用带有 -O2 标志的 clang 13 奇怪地优化以下代码? 使用带有 clang 的较低优化设置和 gcc 的所有优化设置,我得到了“John: 5”的预期打印输出,但是,使用 clang -O2 或更大的优化标志,我得到了“: 5”的输出。 我的代码是否有我不知道的未定义行为? 奇怪的是,如果我用 -fsanitize=undefined 编译代码,代码将按预期工作。 我什至应该如何尝试诊断这样的问题? 任何帮助是极大的赞赏。

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

typedef size_t usize;

typedef struct String {
    char *s;
    usize len;
} String;

String string_new(void) {
    String string;
    char *temp = malloc(1);
    if (temp == NULL) {
        printf("Failed to allocate memory in \"string_new()\".\n");
        exit(-1);
    }
    string.s = temp;
    string.s[0] = 0;
    string.len = 1;
    return string;
}

String string_from(char *s) {
    String string = string_new();
    string.s = s;
    string.len = strlen(s);
    return string;
}

void string_push_char(String *self, char c) {
    self->len = self->len + 1;
    char *temp = realloc(self->s, self->len);
    if (temp == NULL) {
        printf("Failed to allocate memory in \"string_push_char()\".\n");
        exit(-1);
    }
    self->s[self->len - 2] = c;
    self->s[self->len - 1] = 0;
}

void string_free(String *self) {
    free(self->s);
}

int main(void) {
    String name = string_new();
    string_push_char(&name, 'J');
    string_push_char(&name, 'o');
    string_push_char(&name, 'h');
    string_push_char(&name, 'n');

    printf("%s: %lu\n", name.s, name.len);

    string_free(&name);

    return 0;
}

您的string_push_char调用realloc但随后继续使用旧指针。 如果重新分配就地发生,这通常会顺利进行,但是如果内存块被移动,这当然是未定义的行为。

然而,Clang 有一个(有争议的)优化,它假设传递给realloc的指针总是无效的,因为你应该使用返回的指针。

解决方案是在空检查后将temp分配回self->s

作为旁注,您的string_from已完全损坏,您应该将其删除并从头开始重新考虑。

我会用有点不同的方式来做。

typedef size_t usize;

typedef struct String 
{
    usize len;
    char str[];
} String;


String *string_from(char *s) 
{
    usize size = strlen(s);
    String *string = malloc(sizeof(*string) + size + 1);
    if(string)
    {
        string -> len = size + 1; //including null character
        strcpy(string -> str, s);
    }
    return string;
}

String *string_push_char(String *self, char c) {
    usize len = self ? self->len : 1;

    self = realloc(self, len + 1);
    if(self)
    {
        self -> len = len + 1;
        self -> str[self -> len - 2] = c; 
        self -> str[self -> len - 1] = 0; 
    }
    return self;
}

void string_free(String *self) {
    free(self);
}

int main(void) {
    String *str = NULL;
    /* add some allocation checks same as with realloc function (temp pointer etc) */
    str = string_push_char(str, 'J');
    str = string_push_char(str, 'o');
    str = string_push_char(str, 'h');
    str = string_push_char(str, 'n');

    printf("%s: %zu\n", str -> str, str -> len);

    string_free(str);

    return 0;
}

https://godbolt.org/z/4ardvGcxa

在您的代码中,您有很多问题:

String string_from(char *s) {
    String string = string_new();
    string.s = s;
    string.len = strlen(s);
    return string;
}

此函数将立即创建内存泄漏,并将(很可能)不可重新分配(并且可能不可修改)的内存块分配给稍后您可能会尝试重新分配的结构。

除了@Sebastian Redl 的回答之外,我还可以补充说,根据 C17 7.22.3.5,代码具有未定义的行为:

realloc 函数释放 ptr 指向的旧对象,并返回一个指向大小由 size 指定的新对象的指针。

这是在 C90 中没有详细说明并在 C99 中默默澄清的事情之一。 来自 C99 基本原理 V5.10 7.20.3.4:

C99的一个新特性:修改了realloc函数,明确指向的对象被释放,新对象被分配,新对象的内容与旧对象的内容相同,直到较小的两种尺寸。 C89 尝试指定新对象与旧对象是同一对象,但可能具有不同的地址。 这与标准的其他部分相冲突,这些部分假设对象的地址在其生命周期内是不变的。 此外,当大小为零时支持实际分配的实现在这种情况下不一定返回空指针。 C89 似乎需要一个空返回值,委员会认为这太严格了。

值得注意的是clang -O3 -std=c90 -pedantic-errors仍然崩溃,所以这段代码从来没有在任何 C 版本的 clang 中工作过。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM