单线程代码中的编译器屏障是否必要?

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)

chq*_*lie 6

该代码具有未定义的行为:您将其转换list为 a(struct list_elem *)并通过不同的类型访问其字段。这违反了严格的别名规则。编译器可能会生成看起来按预期工作而无需优化的代码,但是当您使用 启用优化时-O2,编译器会对代码进行更深入的分析,并假设您没有违反此规则。因此,生成的代码可能不再按预期工作。

为了避免这种未定义的行为,要么

  • 告诉编译器您打算打破(或不完全理解)严格的别名规则-fno-strict-aliasing。您的程序仍然具有未定义的行为,但编译器会假设您可能违反此特定规则。

  • 或者根据 nm 修改代码,大家会在 Reddit的评论中看到,以避免强制转换和类型双关。