Goforyouqp  

 `volatile`是一个C/C++关键字,它用于告诉编译器当前变量是易变的,需要在每次使用时都从内存中重新获取值,而不是使用缓存中的旧值。

一般来说,对于定义在函数中的自动变量,编译器会尽量利用寄存器来提高访问速度,这样就会使得该变量的值可能被缓存起来,不一定会立即被写入内存。如果这时候要访问这个变量的值,就可能会出现错误的结果。特别是在并发编程或者嵌入式开发等场景下,可能会对内存进行随时修改,此时使用`volatile`关键字就可以防止出现这种错误。

使用`volatile`关键字时需要注意,它仅仅告诉编译器该变量是易变的,不应该使用寄存器来存储其值,但并不保证进程访问该变量的正确性,仍然需要考虑对多线程的并发安全问题,要遵循原子操作和加锁等操作。

可以举一个多线程环境下使用`volatile`关键字的例子。假设有一个共享资源`count`,多个线程同时访问它,其中一个线程会不断地修改`count`的值,其他线程需要读取`count`的值。这时候可以使用`volatile`关键字来确保其他线程读取到的都是最新的`count`的值,而不是之前被缓存的值。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define NTHREADS 10

volatile int count = 0;

void *counter(void *arg) {
    while (1) {
        sleep(1);  // 模拟计数的时间
        count += 1;
    }
    return NULL;
}

int main() {
    pthread_t threads[NTHREADS];
    int i, ret;

    // 创建NTHREADS个线程,都执行counter函数
    for (i = 0; i < NTHREADS; ++i) {
        ret = pthread_create(&threads[i], NULL, counter, NULL);
        if (ret != 0) {
            fprintf(stderr, "Error creating thread %d\n", i);
            exit(EXIT_FAILURE);
        }
    }

    // 主线程负责读取count的值,并输出
    while (1) {
        printf("count = %d\n", count);
        sleep(1);
    }

    // 等待子线程结束
    for (i = 0; i < NTHREADS; ++i) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}

在上面的代码中,`count`变量定义时使用了`volatile`关键字,这样子线程每次修改`count`值之后,其他线程读取到的都是最新的`count`值。如果不使用`volatile`关键字,则可能读取到旧值,导致错误的结果。

此外,volatile还有阻止程序优化的作用。

在 C/C++ 中,`volatile` 关键字用于告诉编译器某个变量可能会在程序的外部被修改,从而避免编译器对该变量进行过度的优化。

如果没有使用 `volatile`,编译器可能会认为某个变量的值在整个程序执行期间都没有发生变化,从而对其进行优化,如将其存储在寄存器中,直接使用寄存器中的值而不是读取该变量的实际值。但如果该变量是被其他线程或硬件设备修改的(例如一个硬件 I/O 寄存器的值),那么编译器的优化可能会导致程序出错。

因此,使用 `volatile` 关键字可以告诉编译器,该变量的值是不稳定的,可能会在程序执行期间被修改,编译器必须每次读取该变量的实际值而不是使用寄存器中的旧值。

需要注意的是,`volatile` 并不能保证线程安全或原子性,它只能避免编译器对该变量的过度优化,而并不能保证其他线程或硬件设备对其的修改操作。如果需要保证线程安全和原子性,通常需要使用更高级别的同步原语,例如互斥锁、条件变量、原子变量等。

下面是一个简单的例子,用来说明 `volatile` 关键字是如何阻止编译器进行优化的。

#include <stdio.h>

volatile int count = 0;  // 定义一个 volatile 变量 count

int main()
{
    int i;
    for(i = 0; i < 1000000; i++) {
        count++;  // 对 count 进行单纯的自增操作
    }
    printf("count = %d\n", count);
    return 0;
}

在这个程序中,我们定义了一个全局变量 `count`,并使用 `volatile` 关键字修饰它,告诉编译器该变量可能会被程序外部修改。

在 `main` 函数中,我们对 `count` 变量进行 1000000 次自增操作,并最终输出它的值。如果没有使用 `volatile` 关键字,编译器可能会认为程序中没有其他地方对 `count` 进行修改,从而对它进行过度优化。

为了验证这一点,我们可以使用 `-O3` 选项开启 GCC 的最高级别优化,再使用 `objdump` 命令查看编译后的汇编代码:

$ gcc -O3 -c -o test.o test.c
$ objdump -d test.o

编译后的汇编代码中,可以看到很多关于 `count` 的读取和运算都被优化成了寄存器操作,如下所示:

0000000000000000 <main>:
   0:   b8 c0 9a 3b 00          mov    $0x3b9ac0,%eax
   5:   89 c3                   mov    %eax,%ebx
   7:   b9 38 00 00 00          mov    $0x38,%ecx
   c:   83 e9 01                sub    $0x1,%ecx
  ...
  6e:   83 e8 01                sub    $0x1,%eax
  71:   75 f6                   jne    69 <main+0x69>
  73:   89 d8                   mov    %ebx,%eax
  75:   c3                      retq

可以看到,编译器将 `count` 的值存储在 `%eax` 寄存器中,并且每次自增操作都是直接修改该寄存器的值,而没有读取 `count` 的实际值。

但是,由于我们使用了 `volatile` 关键字,编译器会在编译期间禁止对 `count` 进行过度优化,从而保证每次自增都会读取 `count` 的实际值,即使程序外部可能会修改 `count`。

在经过 `-O3` 优化后编译后的汇编代码,即使加上 `volatile`,某些语句仍然可能会发生优化。

因此,需要注意,使用 `volatile` 并不能完全保证程序的正确性,只能在特定场合下避免编译器对某些变量进行过度优化。如果需要确保程序正确性,请使用更加严格的同步原语,如互斥锁、条件变量等。

posted on 2023-06-20 16:25  嵌入式小白-小黑  阅读(728)  评论(0编辑  收藏  举报  来源