Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Warning sur l'évaluation d'une expression de type pointeur de fonction ("opera tion may be undefined")

23 réponses
Avatar
Xavier Roche
Bonsoir à tous,

Soit le petit programme suvant:

static void bar(void) {}
int main(void) {
void (*my_bar)(void);
(my_bar = bar, my_bar)();
return 0;
}

gcc me dit gentiment:
1.c:8: warning: operation on `my_bar' may be undefined

Question: en quoi la ligne "(my_bar = bar, my_bar)()" est-elle ambigüe ?
J'ai beau ouvrir grand mes yeux à cette heure tardive, je ne vois pas.

10 réponses

1 2 3
Avatar
Pierre Maurette
[...]
Encore une fois je comprend que dans cet exemple:
(arg1 = 42, function = my_function, function)(arg1);
on ait des problèmes (parce que ICI arg1 peut être placé sur la pile avant le
calcul du pointeur de fonction, c'est à dire avant l'évaluation de la
séquence ",")


Et même fontion(arg1) peut-être appelée avant cette évaluation de la
séquence ",". C'est ce que semble considérer gcc, et je pense de moins
en moins à un bug.

arg1 = 42, function = my_function, function(arg1);

ira bien.

--
Pierre Maurette

Avatar
Xavier Roche
C'est peut-être simple à modifier dans vous macros ?


Euh non, pas du tout, mais je m'en suis sorti autrement :p
Ce qui est bête, c'est que le seul interêt de "l'opérateur comma", mis à
part les for(), c'est bien de faire des effets de bords dans des macros.

Je cite au passage le K&R, ça fait toujours bien de faire des citations
de qualité:
"Comma operators should be used sparingly. The most suitable uses are
for constructs strongly related to each other, as in the for loop in
reverse, and in macros where a multistep computation has to be a
single expression."

Avatar
Xavier Roche
Et même fontion(arg1) peut-être appelée avant cette évaluation de la
séquence ",".


Euh non, non. Pour appeler la fonction, il faut qu'il évalue la (), et
l'interieur est clairement spécifié dans le standard.

Avatar
Pierre Maurette
Et même fontion(arg1) peut-être appelée avant cette évaluation de la
séquence ",".


Euh non, non. Pour appeler la fonction, il faut qu'il évalue la (), et
l'interieur est clairement spécifié dans le standard.


C'est ce que je pense également. Enfin, je ne sais plus trop que
penser. Mais le fait que le warning diparaisse entre
b = (a = 2, f = g, f)(a);
et
a = 2, f = g, b = f(a);
laisse peu de doute à mon avis sur le sens que gcc lui donne. Et la
seconde expression décrit exactement ce que vous voulez.

--
Pierre Maurette


Avatar
James Kanze
Xavier Roche wrote:
Donc, tu essaies d'appeler avec l'ancien my_bar qui n'est
pas encore initialisé.



Qui n'est /peut être/ ("MAY be undefined") pas encore initialisé.


Bon, apparamment POSIX/ANSI ne standardise pas non plus
l'ordre d'execution entre les "," et le compilateur peut
évaluer la partie droite avant d'executer la gauche, si j'ai
bien tout pigé.


Si, mais les points de séquencement ne définissent qu'un ordre
partiel. Ici, je ne suis pas trop sûr, mais je crois que g++
est allé un peu trop loin, mais le point de séquencement dû à
l'opérateur virgule ne définit un ordre qu'entre les deux
expressions opérandes de cet opérateur. Ici, toute la reste de
l'expression utilise le résultat de cet opérateur. Et le
résultat, c'est l'expression de droite, et il y a bien un point
de séquencement entre elle et l'expression de gauche. C'est donc
bien défini.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
James Kanze
Xavier Roche wrote:
http://msdn2.microsoft.com/en-us/library/2bxt6kc4.aspx
ne parle que de la liste d'arguments, mais à aucun endroit ne
précise que le calcul d'un pointeur de fonction ne doit être
mené avant l'appel.



Euh, c'est le *résultat* de la () qui est utilisé pour appeler
la fonction, donc c'est normal qu'il soit calculé avant, il
n'y a pas d'ambigüité ici.


Oui et non. Le problème ne vient pas du resultat en soi (qui est
bien garanti), mais sur le moment où les effets de borde peut
avoir lieu. Globalement, si on écrit quelque chose comme (a,b)
(ou le virgule est un opérateur), il est garanti que 1) les
effets de borde de a ont lieu avant l'évaluation de b, et 2) les
effets de borde de a ont lieu avant la fin de l'expression
complète. Si j'ai donc quelque chose du genre (a,b)+c, et c
utilse les effets de bord de a, j'ai bien un comportement
indéfini. En revanche, le résultat de (a,b), c'est le résultat
de b. Je ne peux donc pas exécuter les parties de l'expression
qui dépend de cette expression avant d'avoir évalué b. Et le
point de séquencement assure qu'avant d'évaluer b, les effets de
bord d'a ont bieu eu lieu.

Je ne comprend donc toujours pas le warning.


Bon, c'est quand même un peu sodomite, tout ça ;-)



Oui et non - cela peut aussi indiquer une initialisation qui
peut potentiellement ne pas se faire, et donc potentiellement
un joli crash sur un autre compilateur. Le tout est de
comprendre où se situe le hic.


Tout à fait. Même s'il s'avère que l'avertissement n'est pas
conforme à la norme, il faut considérer que les auteurs de g++
croyaient qu'il était, et qu'ils ont écrit leur optimisateur en
conséquence. Et le fait que ce soit toi qui ait raison, et non
le compilateur, ne va pas faire marcher ton code.

Encore une fois je comprend que dans cet exemple: (arg1 = 42,
function = my_function, function)(arg1); on ait des problèmes
(parce que ICI arg1 peut être placé sur la pile avant le
calcul du pointeur de fonction, c'est à dire avant
l'évaluation de la séquence ",")


Tout à fait. Il n'y a pas de point de séquencement entre
l'évaluation de arg1 comme paramètre et son écriture dans les
parenthèse.

Note bien que dans ton exemple, il n'y a pas de point de
séquencement non plus entre l'appel de la fonction et l'écriture
dans l'affectation. Mais... pour appeler la fonction, il faut
évaluer l'expression qui détermine l'adresse de la fonction. Et
il y a bien un point de séquencement entre cette expression et
l'affectation. Les dépendances absolues introduisent un ordre
aussi -- pas sur les effets de bords directement, mais sur
l'évaluation des sous-expressions. Et les points de séquencement
qui concernent les sous-expressions ne sont pas annulés pour
autant.

Mais là, l'opérateur "," GARANTI que la partie gauche sera
évaluée AVANT la partie droite, renvoyée comme valeur (et avec
le bon type)


Tout à fait.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
James Kanze
Pierre Maurette wrote:
[...]
Encore une fois je comprend que dans cet exemple:
(arg1 = 42, function = my_function, function)(arg1);
on ait des problèmes (parce que ICI arg1 peut être placé sur la pile
avant le calcul du pointeur de fonction, c'est à dire avant
l'évaluation de la séquence ",")



Et même fontion(arg1) peut-être appelée avant cette évaluation
de la séquence ",".


Comment ? La fonction qu'on appelle, c'est le résultat de
l'opérateur virgule. Il faut donc évaluer l'opérateur virgule
d'abord. Et bingo, le point de séquencement apparaît.

Ce que tu as écrit, c'est à peu près comme si tu disais que dans
l'expression : a[i]() (où a a le type void (*[])()), le
compilateur pouvait appeler la fonction avant d'avoir évalué a
et i. L'ordre dans l'expression en question est en premier lieu
déterminé par la dépendence. (Note bien que cet ordre-là
n'impose pas que les effets de bord ont eu lieu. C'est bien la
séquence point que cet ordre introduit que fasse cette
garantie.)

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
Pierre Maurette
Pierre Maurette wrote:
[...]
Encore une fois je comprend que dans cet exemple:
(arg1 = 42, function = my_function, function)(arg1);
on ait des problèmes (parce que ICI arg1 peut être placé sur la pile
avant le calcul du pointeur de fonction, c'est à dire avant
l'évaluation de la séquence ",")


Et même fontion(arg1) peut-être appelée avant cette évaluation
de la séquence ",".


Comment ? La fonction qu'on appelle, c'est le résultat de
l'opérateur virgule. Il faut donc évaluer l'opérateur virgule
d'abord. Et bingo, le point de séquencement apparaît.

Ce que tu as écrit, c'est à peu près comme si tu disais que dans
l'expression : a[i]() (où a a le type void (*[])()), le
compilateur pouvait appeler la fonction avant d'avoir évalué a
et i. L'ordre dans l'expression en question est en premier lieu
déterminé par la dépendence. (Note bien que cet ordre-là
n'impose pas que les effets de bord ont eu lieu. C'est bien la
séquence point que cet ordre introduit que fasse cette
garantie.)


C'est bien ce qu'il me semblait au départ. J'ai même fait des
raisonnements de ce type. Simplement mon faible background en C fait
que je ne conclus jamais seul à un bug du compilateur, et qu'au
contraire un comportement inattendu de ce dernier me pousse à remettre
en question mes connaissances.
Or, ici, toutes les manips consistant à faire apparaître et disparaître
le warning semblaient converger vers le fait que gcc considérait que la
fonction pouvait être appelée avant l'évaluation de l'expression
contenant l'opérateur comma.

Il y aurait donc bug, il faut donc faire avec. On a deux solutions. Si
on peut se le permettre, on laisse gueuler. Simplement, on (enfin, moi)
ne sait pas si le bug est juste dans la génération des warnings ou plus
profond, le warning ne traduisant un mauvais comportement du code
optimisé. A ce moment-là, autant abandonner:
(f = g, f)();
et écrire:
f = g, f();

--
Pierre Maurette



Avatar
Emmanuel Delahaye
Bonsoir à tous,

Soit le petit programme suvant:

static void bar(void) {}
int main(void) {
void (*my_bar)(void);
(my_bar = bar, my_bar)();
return 0;
}

gcc me dit gentiment:
1.c:8: warning: operation on `my_bar' may be undefined

Question: en quoi la ligne "(my_bar = bar, my_bar)()" est-elle ambigüe ?
J'ai beau ouvrir grand mes yeux à cette heure tardive, je ne vois pas.


Je ne comprends pas non plus. Y'a-t-il une raison particulière de ne pas
faire ceci ?

#include <stdio.h>

static void bar(void)
{
puts("bar");
}

int main(void)
{
void (*my_bar)(void) = bar;

my_bar();
return 0;
}

--
A+

Emmanuel Delahaye

Avatar
Xavier Roche
Je ne comprends pas non plus. Y'a-t-il une raison particulière de ne pas
faire ceci ?


Oui. (le vrai code n'est pas aussi trivial, c'est une macro qui fait un
effet de bord avant de retourner une valeur, pour être utilisé dans une
expression évaluable)

1 2 3