我试图通过一个例子来理解 volatile
的行为和 C 中的编译器优化。
为此,我引用了:
以上所有帖子至少有一个与信号处理程序相关的答案,因此为此,我编写了一个简单的代码来实际实现和观察 Linux 中的行为,以便理解。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
int counter = 0;
void *thread0_func(void *arg)
{
printf("Thread 0\n");
while(1)
{
}
return NULL;
}
void *thread1_func(void *arg)
{
printf("Thread 1\n");
while(counter == 0)
{
printf("Counter: %d\n", counter);
usleep(90000);
}
return NULL;
}
void action_handler(int sig_no)
{
printf("SigINT Generated: %d\n",counter);
counter += 1;
}
int main(int argc, char **argv)
{
pthread_t thread_id[2];
struct sigaction sa;
sa.sa_handler = action_handler;
if(sigaction(SIGINT, &sa, NULL))
perror("Cannot Install Sig handler");
if(pthread_create(&thread_id[0], NULL, thread0_func, NULL))
{
perror("Error Creating Thread 0");
}
if(pthread_create(&thread_id[1], NULL, thread1_func, NULL))
{
perror("Error Creating Thread 0");
}
else
{
}
while(1)
{
if(counter >= 5)
{
printf("Value of Counter is more than five\n");
}
usleep(90000);
}
return (0);
}
此代码仅供学习和理解。
我尝试使用以下方法编译代码:
gcc -O3 main.c -o main -pthread
但是编译器没有作用于全局变量counter
,也没有优化它。
我原以为 *thread1_func
会在一个永远的循环中执行,而 if (counter >= 5)
永远不会为真。
我在这里错过了什么?
GCC 版本:gcc 版本 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4)
最佳答案
您对 counter
值的 if 测试穿插着对 usleep
和 printf
的调用。这些是不透明的库调用。编译器无法看穿它们,因此它必须假定它们可以访问 counter
外部变量,因此它必须在这些调用之后重新加载 counter
变量。
如果将这些调用移出,代码将按预期得到优化:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
int counter = 0;
void *thread0_func(void *arg)
{
printf("Thread 0\n");
while(1)
{
}
return NULL;
}
void *thread1_func(void *arg)
{
printf("Thread 1\n");
unsigned i=0;
while(counter == 0)
{
i++;
}
printf("Thread 1: %d, i=%u\n", counter, i);
return NULL;
}
void action_handler(int sig_no)
{
printf("SigINT Generated: %d\n",counter);
counter += 1;
}
int main(int argc, char **argv)
{
pthread_t thread_id[2];
struct sigaction sa;
sa.sa_handler = action_handler;
if(sigaction(SIGINT, &sa, NULL))
perror("Cannot Install Sig handler");
if(pthread_create(&thread_id[0], NULL, thread0_func, NULL))
{
perror("Error Creating Thread 0");
}
if(pthread_create(&thread_id[1], NULL, thread1_func, NULL))
{
perror("Error Creating Thread 0");
}
else
{
}
while(1)
{
if(counter >= 5)
{
printf("Value of Counter is more than five\n");
}
usleep(90000);
}
return (0);
}
即使你把计数器变量设为static
,编译器仍然不会优化,因为虽然外部库肯定看不到计数器变量,但外部调用理论上可能有互斥锁,这将允许另一个线程在没有数据竞争的情况下更改变量。现在 usleep
和 printf
都不是互斥锁的包装器,但是编译器不知道,也不做线程间优化,所以它必须是保守的和在调用后重新加载计数器变量,重新加载会阻止您期望的优化。
当然,一个简单的解释是,如果信号处理程序执行,您的程序是未定义的,因为您应该制作 counter
volatile sig_atomic_t
并且您应该已使用 _Atomic
或互斥锁同步您对它的线程间访问——在未定义的程序中,一切皆有可能。
关于c - 为什么 gcc 不优化全局变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43570984/