2017-05-10 3 views
2

Le code suivant, si prétraité avec gcc -E, produit des erreurs de _Pragma("GCC error"):Erreur GCC _Pragma se déclenche s'il apparaît dans une étape de prétraitement intermédiaire?

_Pragma("GCC error \"ERROR\"") // error 

#define MACRO_ERROR _Pragma("GCC error \"MACRO_ERROR\"") 
MACRO_ERROR // error 

#define VOID(arg) 
VOID(_Pragma("GCC error \"VOID_ERROR\"")) // no error 

#define MACRO_VOID_ERROR VOID(_Pragma("GCC error \"MACRO_VOID_ERROR\"")) 
MACRO_VOID_ERROR // no error 

#define FORWARD(macro, arg) macro(arg) 
FORWARD(VOID, _Pragma("GCC error \"FORWARD_VOID_ERROR\"")) // error 

#define MACRO_FORWARD_VOID_ERROR FORWARD(VOID, _Pragma("GCC error \"MACRO_FORWARD_VOID_ERROR\"")) 
MACRO_FORWARD_VOID_ERROR // error 

FORWARD(VOID, _Pragma("GCC error \"FORWARD_VOID_ERROR\"")) et MACRO_FORWARD_VOID_ERROR erreurs produisent même si l'opérateur de pragma est pas dans l'expansion finale (qui est vide).

Est-ce que ce comportement est attendu? Par comparaison, VOID(_Pragma("GCC error \"VOID_ERROR\"")) et MACRO_VOID_ERROR ne produisent pas d'erreurs. Il semble que c'est parce que l'opérateur pragma est "prétraité assez vite" avec ceux-ci.

Quelle est la règle derrière cela?

Auparavant, je supposais que l'opérateur pragma est sans effet s'il apparaît uniquement dans les étapes d'expansion intermédiaires. Évidemment, c'est faux, au moins pour mon gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4).

sortie de gcc -E (lignes vides enlevées):

# 1 "<stdin>" 
# 1 "<built-in>" 
# 1 "<command-line>" 
# 1 "/usr/include/stdc-predef.h" 1 3 4 
# 1 "<command-line>" 2 
# 1 "<stdin>" 
# 1 "<stdin>" 
<stdin>:1:11: error: ERROR 
# 1 "<stdin>" 
# 4 "<stdin>" 
<stdin>:4:11: error: MACRO_ERROR 
# 4 "<stdin>" 
# 13 "<stdin>" 
<stdin>:13:11: error: FORWARD_VOID_ERROR 
# 13 "<stdin>" 
# 16 "<stdin>" 
<stdin>:16:11: error: MACRO_FORWARD_VOID_ERROR 
# 16 "<stdin>" 

Répondre

1

Ceci est en fait comme prévu (ou du moins, ce que j'attendais), bien qu'il soit unintuitive parce que vous mélangez des effets secondaires au niveau d'expression dans un langage qui a été conçu à l'origine pour être complètement sans effets secondaires et partiellement paresseux.

En se référant à la norme C plutôt que de la documentation de GCC, nous pouvons trouver les éléments suivants dans 6.10.3.1:

Un paramètre dans la liste de remplacement, si elle est précédée par un # ou ## prétraiter jeton ou suivi d'un ## le jeton de prétraitement (voir ci-dessous) est remplacé par l'argument correspondant après que toutes les macros contenues dans celui-ci aient été développées. Avant d'être substitués, les jetons de prétraitement de chaque argument sont complètement remplacés par des macros comme s'ils formaient le reste du fichier de prétraitement; aucun autre jeton de prétraitement n'est disponible.

La pièce maîtresse du phrasé ambigu est ici que la norme ne dit qu'un argument est étendu (« comme si elle formait le reste du fichier ») avant d'être substitué. Il ne dit pas explicitement qu'un argument doit être développé s'il n'est pas du tout substitué, et puisque la première partie du paragraphe indique clairement que la façon dont un argument est traité dépend du contexte dans lequel il est effectivement Utilisé par la macro, c'est une optimisation juste à faire (assez nécessaire pour gérer des bibliothèques de métaprogrammation complexes comme Boost, aussi).

« Comme ils forment le reste du fichier » est la clé pour laquelle l'exécution de l'opérateur _Pragma doit se produire avant la substitution dans un argument: 5.1.1.2 listes _Pragma exécution comme appartenant à la même phase que le remplacement macro, donc si le remplacement est appliqué à une séquence de jetons donnée, ce qui est certainement le cas lorsque l'argument est substitué, ainsi devrait _Pragma d'exécution.

Il est tout à fait surprenant que le _Pragma devrait fonctionner dans le cas FORWARD, car il doit être évalué, et ses effets appliqués, deux étapes avant la VOID macro est pris pour l'expansion au cours rescan. Il est potentiellement ambigu s'il devrait être appliqué dans le cas de l'appel direct à VOID, mais puisque la norme a explicitement des macros décider comment gérer un argument basé sur la façon dont l'argument est utilisé, il est logique qu'il devrait être autorisé pour un argument c'est complètement un utilisé pour ne jamais être évalué du tout, même si cela n'est pas spécifié directement.