簡體   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