[英]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.