Erw*_*inP 1 c multithreading gcc compiler-optimization barrier
我有一段带有双向链表的代码(受linux列表的启发),无需编译器优化即可工作。当我激活编译器优化时,它失败了。
我的程序将一个元素添加到列表中,然后在列表不为空时循环遍历列表。在循环体中,获取第一个元素,然后断言检查该元素是否在列表中,然后删除该元素。此断言因编译器优化而失败 ( -O2)。有了编译器障碍,就不会有错误。
我假设在错误情况下,编译器不会在每次循环迭代中重新加载列表的元素while (!list_empty(&li))。当我查看主函数的编译代码时,我发现没有条件代码,并且__assert_fail在最后被调用。但为什么?我一直认为在单线程代码中编程是安全的,没有障碍。
这是我的代码:
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <assert.h>
struct list_elem {
struct list_elem *next;
struct list_elem *prev;
};
struct list {
struct list_elem *next;
struct list_elem *prev;
};
static inline void init_list_head(struct list *list)
{
list->next = (struct list_elem *)list;
list->prev = (struct list_elem *)list;
}
static inline void init_list_elem(struct list_elem *elem)
{
elem->next = NULL;
elem->prev = NULL;
}
static inline void *list_entry(struct list_elem *elem, size_t offset)
{
return (char *)(elem) - offset;
}
static inline int list_empty(struct list *list)
{
return list->next == (struct list_elem *)list;
}
static inline struct list_elem *first_list_elem(struct list *list)
{
return list->next;
}
static inline int elem_not_in_list(struct list_elem *elem)
{
return elem->next == NULL;
}
static inline void __list_add(struct list_elem *elem, struct list_elem *prev, struct list_elem *next)
{
next->prev = elem;
elem->next = next;
elem->prev = prev;
prev->next = elem;
}
static inline void list_add(struct list_elem *elem, struct list *list)
{
__list_add(elem, (struct list_elem *)list, list->next);
}
static inline void list_del(struct list_elem *elem)
{
elem->prev->next = elem->next;
elem->next->prev = elem->prev;
elem->next = NULL;
elem->prev = NULL;
}
struct record {
int value;
struct list_elem link;
};
struct record r1;
struct list li;
int main(int argc, char *argv[])
{
init_list_head(&li);
init_list_elem(&r1.link);
r1.value = 1;
list_add(&r1.link, &li);
while (!list_empty(&li)) {
struct record *r = list_entry(first_list_elem(&li), offsetof(struct record, link));
assert(!elem_not_in_list(&r->link));
list_del(&r->link);
r1.value += argc;
// compiler barrier
//asm volatile("": : :"memory");
}
return r1.value;
}
Run Code Online (Sandbox Code Playgroud)
编译器:
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <assert.h>
struct list_elem {
struct list_elem *next;
struct list_elem *prev;
};
struct list {
struct list_elem *next;
struct list_elem *prev;
};
static inline void init_list_head(struct list *list)
{
list->next = (struct list_elem *)list;
list->prev = (struct list_elem *)list;
}
static inline void init_list_elem(struct list_elem *elem)
{
elem->next = NULL;
elem->prev = NULL;
}
static inline void *list_entry(struct list_elem *elem, size_t offset)
{
return (char *)(elem) - offset;
}
static inline int list_empty(struct list *list)
{
return list->next == (struct list_elem *)list;
}
static inline struct list_elem *first_list_elem(struct list *list)
{
return list->next;
}
static inline int elem_not_in_list(struct list_elem *elem)
{
return elem->next == NULL;
}
static inline void __list_add(struct list_elem *elem, struct list_elem *prev, struct list_elem *next)
{
next->prev = elem;
elem->next = next;
elem->prev = prev;
prev->next = elem;
}
static inline void list_add(struct list_elem *elem, struct list *list)
{
__list_add(elem, (struct list_elem *)list, list->next);
}
static inline void list_del(struct list_elem *elem)
{
elem->prev->next = elem->next;
elem->next->prev = elem->prev;
elem->next = NULL;
elem->prev = NULL;
}
struct record {
int value;
struct list_elem link;
};
struct record r1;
struct list li;
int main(int argc, char *argv[])
{
init_list_head(&li);
init_list_elem(&r1.link);
r1.value = 1;
list_add(&r1.link, &li);
while (!list_empty(&li)) {
struct record *r = list_entry(first_list_elem(&li), offsetof(struct record, link));
assert(!elem_not_in_list(&r->link));
list_del(&r->link);
r1.value += argc;
// compiler barrier
//asm volatile("": : :"memory");
}
return r1.value;
}
Run Code Online (Sandbox Code Playgroud)
编译命令:
gcc version 11.2.0 (GCC)
Target: arm-linux-musleabihf
Run Code Online (Sandbox Code Playgroud)
编译后的代码:
00010290 <main>:
10290: e59f1030 ldr r1, [pc, #48] ; 102c8 <main+0x38>
10294: e3a0c000 mov ip, #0
10298: e2800001 add r0, r0, #1
1029c: e92d4010 push {r4, lr}
102a0: e3a02057 mov r2, #87 ; 0x57
102a4: e5810008 str r0, [r1, #8]
102a8: e5811000 str r1, [r1]
102ac: e5811004 str r1, [r1, #4]
102b0: e581c00c str ip, [r1, #12]
102b4: e581c010 str ip, [r1, #16]
102b8: e59f300c ldr r3, [pc, #12] ; 102cc <main+0x3c>
102bc: e59f100c ldr r1, [pc, #12] ; 102d0 <main+0x40>
102c0: e59f000c ldr r0, [pc, #12] ; 102d4 <main+0x44>
102c4: ebffffe8 bl 1026c <__assert_fail@plt>
102c8: 0002103c .word 0x0002103c
102cc: 000104dc .word 0x000104dc
102d0: 000104b0 .word 0x000104b0
102d4: 000104c0 .word 0x000104c0
Run Code Online (Sandbox Code Playgroud)
该代码具有未定义的行为:您将其转换list为 a(struct list_elem *)并通过不同的类型访问其字段。这违反了严格的别名规则。编译器可能会生成看起来按预期工作而无需优化的代码,但是当您使用 启用优化时-O2,编译器会对代码进行更深入的分析,并假设您没有违反此规则。因此,生成的代码可能不再按预期工作。
为了避免这种未定义的行为,要么
告诉编译器您打算打破(或不完全理解)严格的别名规则-fno-strict-aliasing。您的程序仍然具有未定义的行为,但编译器会假设您可能违反此特定规则。
或者根据 nm 修改代码,大家会在 Reddit的评论中看到,以避免强制转换和类型双关。