【问题标题】:Clang 13 -O2 produces weird output while gcc does notClang 13 -O2 会产生奇怪的输出,而 gcc 不会
【发布时间】:2021-12-16 05:55:06
【问题描述】:

有人可以向我解释为什么下面的代码在带有 -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_from 看起来很奇怪。为什么要分配输入指针而不是分配+strcpy()?这也导致了内存泄漏:原来的string.s 丢失了并且永远不会被释放。
  • 如果两个编译器生成的代码行为不同,那么这通常表明您的代码中有一些未定义的行为
  • 在您的string_push_char 中,您永远不会将temp 分配回self-&gt;s。但是realloc 不保证内存会和以前一样。关于“我什至应该如何尝试诊断这样的问题” - 有时仔细阅读代码会有所帮助
  • 另外,realloc 不一定返回相同的指针 - 可以根据需要移动数据。您应该将realloc 的结果分配回self-&gt;s

标签: c gcc clang undefined-behavior


【解决方案1】:

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

但是,Clang 有一个 (controversial) 优化,它假定传递给 realloc 的指针 总是 变得无效,因为您应该改用返回的指针。

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

附带说明,您的 string_from 已完全损坏,您应该删除它并从头开始重新考虑。

【讨论】:

  • @Lundin 好点,完成。
  • 我会重新考虑返回结构而不是指针的想法。
【解决方案2】:

除了@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 版本中运行过。

【讨论】:

    【解决方案3】:

    我会做一些不同的方式。

    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;
    }
    

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

    【讨论】:

    • string_from 也存储了错误的长度。
    猜你喜欢
    • 2022-01-12
    • 1970-01-01
    • 2015-08-18
    • 1970-01-01
    • 2012-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多