Langage C: le rôle du mot-clé volatile

Introduction

1. Introduction à l'optimisation du compilateur

Étant donné que la vitesse d'accès à la mémoire est bien inférieure à la vitesse de traitement du processeur, afin d'améliorer les performances globales de la machine, un cache matériel haute vitesse est introduit sur le matériel pour accélérer l'accès à la mémoire. De plus, l'exécution des instructions dans les CPU modernes n'est pas nécessairement exécutée dans un ordre strict, et les instructions sans corrélation peuvent être exécutées dans le désordre pour utiliser pleinement le pipeline d'instructions de la CPU et augmenter la vitesse d'exécution. Ce qui précède est l'optimisation du niveau matériel .

Regardez l'optimisation au niveau logiciel : l'une est optimisée par le programmeur lors de l'écriture du code, et l'autre est optimisée par le compilateur. 编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。Lors de l'optimisation de la mémoire conventionnelle, ces optimisations sont transparentes et efficaces. La solution aux problèmes causés par l'optimisation du compilateur ou la réorganisation matérielle consiste à définir une barrière mémoire entre les opérations qui doivent être effectuées dans un ordre spécifique du point de vue du matériel (ou d'autres processeurs). Linux fournit une macro-solution. L'ordre d'exécution du le compilateur est un problème.

void Barrier(void)

Cette fonction informe le compilateur d'insérer une barrière mémoire, mais elle n'est pas valide pour le matériel. Le code compilé stockera toutes les valeurs modifiées du registre CPU actuel dans la mémoire, puis la lira à nouveau à partir de la mémoire lorsque les données est nécessaire.

2 、 volatile

La volatilité est toujours liée à l'optimisation. Le compilateur utilise une technique appelée analyse de flux de données, qui analyse où les variables du programme sont affectées, où elles sont utilisées et où elles échouent. Les résultats de l'analyse peuvent être utilisés pour une fusion constante, une propagation constante. et d'autres optimisations, et peuvent être éliminées davantage. Mais parfois, ces optimisations ne sont pas requises par le programme et peuvent être utilisées à ce moment volatile关键字禁止做这些优化.

Deux, explication détaillée de volatile

1. Rôle volatil

L'intention originale de volatile est “易变的”que parce que l'accès aux registres est beaucoup plus rapide que l'accès aux cellules de mémoire, le compilateur optimise généralement pour réduire l'accès à la mémoire, mais il peut lire des données sales. 当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据. Pour être précis, lors de la rencontre d'une variable déclarée par ce mot-clé, le compilateur n'optimisera plus le code qui accède à la variable, afin de fournir un accès stable à des adresses spéciales; si volatile n'est pas utilisé, le compilateur le fera. L'instruction est optimisée. (Pour résumer: le mot clé volatile affecte le résultat de la compilation du compilateur. La variable déclarée avec volatile indique que la variable peut changer à tout moment. Pour les opérations liées à la variable, n'effectuez pas d'optimisation de la compilation pour éviter les erreurs)

2. Regardez deux exemples

1> 告诉compiler不能做任何优化

Par exemple, pour envoyer deux commandes à une certaine adresse:

int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令

Le compilateur de programme ci-dessus peut être optimisé:

int *ip = ...;
*ip = 2;

En conséquence, la première instruction est perdue. Si volatile est utilisé, le compilateur n'autorisera aucune optimisation pour garantir l'intention d'origine du programme:

volatile int *ip = ...;
*ip = 1;
*ip = 2;

Même si vous souhaitez que le compilateur optimise, il ne fusionnera pas les deux instructions d'affectation en une seule. Il ne peut faire que d'autres optimisations.

2> 用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。

Par exemple:

volatile char a;

a=0;

while(!a){
    
    
//do some things;
}

doother();

如果没有 volatile,doother()不会被执行

3. Plusieurs scénarios d'utilisation de variables volatiles

1> 中断服务程序中修改的供其它程序检测的变量需要加volatile;

Par exemple:

static int i=0;

int main(void)
{
    
    
     ...
     while (1){
    
    
		if (i) dosomething();	
	}
}

/* Interrupt service routine. */

void ISR_2(void)
{
    
    
      i=1;
}

L'intention d'ISR_2 est de programmer une interruption est générée, l'appel de fonction dosomething fonction principale, cependant, parce que le compilateur détermine non modifié à l'intérieur de la fonction principale i, il ne peut être effectué qu'une seule fois à partir de il'opération de lecture d'un registre, et chacun ifest déterminé Tout uniquement utiliser le contenu de ce registre “i副本”, ce qui fait que dosomething ne sera jamais appelé. Si vous ajoutez la variable volatilemodifiée, le compilateur lit et écrit des variables pour s'assurer que cela ne sera pas optimisé (exécution positive). Dans cet exemple, iil devrait être ainsi indiqué.

2>多任务环境下各任务间共享的标志应该加volatile

3>存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。

Par exemple:

Supposons qu'un périphérique doit être initialisé et qu'un certain registre de ce périphérique est 0xff800000.

int *output = (unsigned int *)0xff800000;//定义一个IO端口;

int init(void)
{
    
    
	int i;
      
	for(i=0; i< 10; i++){
    
    
		*output = i;
	}
}

Après optimisation par le compilateur, le compilateur pense que la boucle précédente est absurde pendant une demi-journée, et n'a aucun effet sur le résultat final, car à la fin il affecte simplement le pointeur de sortie à 9, donc le compilateur vous donne enfin le compilé le résultat du code équivaut à:

int init(void)
{
    
    
	*output = 9;
}

Si vous initialisez le périphérique externe dans le même ordre que le code ci-dessus, le processus d'optimisation n'atteindra évidemment pas l'objectif. Inversement, si vous ne répétez pas les opérations d'écriture sur ce port, mais répétez les opérations de lecture, le résultat est le même. Une fois le compilateur optimisé, peut-être que votre code ne lit cette adresse qu'une seule fois. Cependant, du point de vue du code, il n'y a pas de problème. À ce stade, en ce qui concerne l'utilisation volatilepour informer le compilateur que la variable est une variable instable, pas pour optimiser l'expérience cette variable de temps.

Par exemple:

volatile  int *output=(volatile unsigned int *)0xff800000;//定义一个I/O端口

En outre, plusieurs situations ci-dessus doivent souvent être considérées en même temps 数据的完整性(les signes interdépendants sont à moitié lus et interrompus et réécrits) 1中可以通过关中断来实现,2中禁止任务调度,3中则只能依靠硬件的良好设计.

4. Plusieurs questions

  • 1) Un paramètre peut-il être constant ou volatil?

Oui, comme un registre d'état en lecture seule. Il est volatil car il peut être modifié de manière inattendue. Il est constant car le programme ne doit pas essayer de le modifier.

  • 2) Un pointeur peut-il être volatil?

Oui, lorsqu'une routine de service d'interruption modifie un pointeur vers un tampon.

5. L'essence de la volatilité

1> Optimisation du compilateur

Dans ce thread, lors de la lecture d'une variable, afin d'améliorer la vitesse d'accès, le compilateur lit parfois la variable dans un registre lors de l'optimisation; plus tard, lorsque la valeur de la variable est prise, la valeur est directement extraite du registre; Lorsque la variable la valeur est modifiée dans ce thread, la nouvelle valeur de la variable sera copiée dans le registre en même temps, afin de maintenir la cohérence.

Lorsque la valeur de la variable change en raison d'autres threads, etc., la valeur du registre ne changera pas en conséquence, ce qui rendra la valeur lue par le programme d'application incompatible avec la valeur réelle de la variable.

Lorsque la valeur de ce registre est modifiée en raison d'autres threads, etc., la valeur de la variable d'origine ne changera pas, ce qui rendra la valeur lue par le programme d'application incompatible avec la valeur réelle de la variable.

2> volatile doit être interprété comme “直接存取原始内存地址”plus approprié, "variable". Cette interprétation est simplement un peu trompeuse.

6. Quel est le problème avec la fonction suivante

int square(volatile int *ptr)
{
    
    
	return *ptr * *ptr;
}

Le but de ce programme est de renvoyer le carré de la valeur pointée par le pointeur ptr. Cependant, comme ptr pointe vers un paramètre volatil, le compilateur générera un code similaire à ce qui suit:

int square(volatile int *ptr)
{
    
    
	int a,b;
	a = *ptr;
	b = *ptr;
	return a * b;
}

Étant donné que la valeur de * ptr peut être modifiée de manière inattendue, a et b peuvent être différents. Par conséquent, ce code peut renvoyer une valeur carrée qui n'est pas celle que vous attendiez! Le code correct est le suivant:

long square(volatile int *ptr)
{
    
    
	int a;
	a = *ptr;
	return a * a;
}

注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

Je suppose que tu aimes

Origine blog.csdn.net/houxiaoni01/article/details/105163980
conseillé
Classement