在这种情况下(C语言),正确使用“volatile”的方法是什么?

3
我有一个结构体,保存着多个指针。这些指针可以被不同的线程改变。这些线程通过将指针指向另一个内存位置而更新结构体,它们永远不会改变所指向的值。根据我对volatile的理解,在结构体中声明这些指针为volatile是有意义的。
读取/更新这些指针的线程的工作方式如下:
1. 复制指针, 只使用我们的副本, 这样如果原始指针更改了, 我们就不会在处理过程中突然使用新的指针。 2. 基于我们的副本指针指向的值创建一个新的指针。 3. 使用原子比较+交换(replace)旧指针和新指针, 除非另一个线程已经更新了它。
我遇到的问题是,当我复制指针时,我得到了一个警告:
"warning: initialization discards 'volatile' qualifier from pointer target type"
编译后的代码似乎可以正常工作,但警告让我感到困扰。我是否错误地使用了volatile?假设我的volatile使用正确,我该如何删除警告?我不想在复制指针的声明中添加volatile,因为没有其他线程会更新我们的副本,所以它真的不是volatile。只有在读取/写入结构体时才是volatile。
这里有一些演示我的问题的演示代码,我正在处理的实际代码要好得多,但也太大了无法发布。现在存在一个明显的内存泄漏,请忽略它,我的实际用例可以正确地跟踪和管理内存。我只关心这个问题中的“volatile”,我不想解决演示中的错误。
gcc -std=gnu99 -pthread test.c && ./a.out
test.c: In function ‘the_thread’:
test.c:22:25: warning: initialization discards ‘volatile’ qualifier from pointer target type [enabled by default]

代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

typedef struct {
    volatile int *i;
} thing;

void init( thing *t ) {
    t->i = malloc( sizeof( int ));
    *(t->i) = 1;
}

void *the_thread( void *args ) {
    thing *t = args;

    for ( int i = 0; i < 100; i++ ) {
        // This generates the 'volatile' warning. But I don't want to make
        // *copy volatile since no other threads will ever change 'copy'.
        // Is this incorrect usage, or do I just need to do something to remove
        // the warning?
        int *copy = t->i;

        int *new = malloc( sizeof( int ));
        *new = (*copy) + 1;

        // Glaring memory leak as old x becomes unreachable, this is a demo,
        // the real program this is for has the issue solved.
        // We do not care if it succeeds or fails to swap for this demo.
        __sync_bool_compare_and_swap( &(t->i), copy, new );
    }
}

int main() {
    thing t;
    init( &t );

    pthread_t a;
    pthread_t b;

    pthread_create( &a, NULL, the_thread, &t );
    pthread_create( &b, NULL, the_thread, &t );

    pthread_join( a, NULL );
    pthread_join( b, NULL );

    return 0;
}
1个回答

3
这是因为volatile int *并不意味着"指向int的易变指针",而是意味着"指向易变的int的指针"。比较一下const char *a = "foo";,它是指向常量字符数据的指针,而不是常量指针。
所以在您的情况下,我认为应该将thing指针设置为volatile,因为它中的字段可以从一个线程的角度"随机"更改。您还可以(如您在评论中所说)将volatile移动到结构体中,使其成为int * volatile i;
当然,您也可以随时使用cdecl来获得即时帮助,以解决这些问题。

嗯,做更多的研究,我应该在结构体中实际使用'int * volatile i'吗?看起来这是我用来使指针本身成为volatile的方法。同时,进行更改后警告消失了...但我想确保它按照我的想法工作... - Exodist
难道不是因为他将一个指向volatile int的指针分配给非volatile int时,赋值给copy吗?使用应该是int copy = *(t->i);? - tinman
@tinman 如果我处理的是整数,那么是的。但这只是一个更复杂的演示。我实际上有包含指向几个层次深度的结构体指针的结构体,在任何级别上指针都可以被任意数量的线程更改,因此我需要指针本身是易失性的。 - Exodist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接