#1751 Le 24/08/2013, à 14:14
- grim7reaper
Re : /* Topic des codeurs [8] */
@grim C'est vrai que c'est sympa le -Wdocumentation de Clang. Ceci dit, Doxygen peut générer ces warnings si on lui demande.
Oui, je sais. D’ailleurs en général je lançais un
doxygen Doxyfile 2> doxygen.log
Mais si je peux avoir ça a la compilation, c’est plus sympa (vu que, entre autre, clang colore la sortie).
Bah écoute, personnellement ça répond bien à mes besoins donc ouais j’en suis satisfait
Après, c’est vrai que si tu n’as pas besoin de faire des trucs un peu complexe niveau tests, je pense que sput peut être suffisant et alors autant rester dessus.
D’ailleurs, je le testerai sûrement à l’occasion.
Hors ligne
#1752 Le 24/08/2013, à 20:41
- grim7reaper
Re : /* Topic des codeurs [8] */
Tiens, je viens de tomber sur un joli bug là
Et comme ça faisait longtemps que je n’était pas tombé sur un truc chiadé comme ça, j’ai eu envie de vous faire partager le raisonnement que j’ai suivi pour en venir à bout (des fois que ça puisse reservir à d’autres).
WARNING: poste à rallonge (long comme un longcat…)
* Contexte
Je suis en train de me coder une liste linéaire simplement chaînée.
Cette liste est générique, mais sans passer par des bidouilles à base de void* comme on le fait traditionnellement. Au lieu de ça, cette fois j’ai décidé de m’inspirer d’un truc assez connu utilisé dans le noyau Linux.
J’ai un programme d’exemple qui sert à montrer un peu comment utiliser l’API de ma liste.
Voici une version minimale (j’ai retiré la gestion d’erreur du malloc + la libération de la mémoire + 2-3 autres trucs car ce n’est pas le sujet ici).
#include <stdio.h>
#include <stdlib.h>
#include "slist.h"
typedef struct
{
unsigned to;
slist_node node;
unsigned from;
} pair_list;
int main(void)
{
slist_list list;
pair_list* elt;
unsigned i;
/* Initialize the list. */
slist_init(&list);
/* Populate the list. */
for(i = 0; i < 2; ++i)
{
elt = malloc(sizeof *elt);
elt->from = i;
elt->to = i+1;
slist_add(&list, &(elt->node));
}
/* Iteration with `slist_foreach_elt`. */
slist_foreach_elt(&list, elt, pair_list, node)
printf("from = %u | to = %u\n", elt->from, elt->to);
return 0;
}
* La découverte
Voyons maintenant comment cela s’execute.
- gcc:
from = 1 | to = 2
from = 0 | to = 1
- clang:
from = 1 | to = 2
from = 0 | to = 1
- gcc -O2
from = 1 | to = 2
from = 0 | to = 1
- clang -O2
from = 1 | to = 2
from = 0 | to = 1
zsh: segmentation fault (core dumped)
Oulà, ça pue ça. Ça pue très très fort…
Quand on a une différence de comportement entre deux compilo’, il n’y a que deux solutions possibles :
1) l’un des deux compilo’ est buggés (peu probable, mais ça arrive).
2) notre code contient un undefined behavior (je vais raccourcir par UB par la suite) ce qui signifie que le compilo’ est libre de faire ce qu’il veut. ABSOLUMENT TOUT ce qu’il veut (et dans ces cas-là, les compilo’ peuvent réagir différement). Ça peut également être signe de unspecified behavior ou de implementation-specified behavior mais en général c’est moins pire que les UB.
Cela dit, il ne faut pas cracher sur les UB car c’est grâce à ça que les compilo’ C peuvent optimiser comme des malades[1][2][3].
Bon étant donné que :
- j’avais connaissance des articles précédemment cités.
- mon bug apparaît seulement avec les optimisations activées (pour clang ça plante en O1, O2 et O3, et avec gcc ça ne plante pas quelque soit le niveau d’optim’).
- je faisait des trucs un peu courant niveau pointeurs, adresse, structure, macro & cie.
Je suis donc direct partie sur solution 2
* Le bug
Bon, lançons donc un coup de notre bon vieux Valgrind sur la version compilé et optimisé par clang (entre temps, j’ai pris soin de recompiler avec le flag -g en plus de -O2 pour avoir les info’ de déboguage).
Voilà le résultat :
==7949== Invalid read of size 4
==7949== at 0x4006A0: main (min_bug.c:34)
==7949== Address 0xfffffffffffffff8 is not stack'd, malloc'd or (recently) free'd
Info’ utile : la ligne 34 c’est le printf dans la boucle).
À priori donc, ma boucle itère un peu trop et va essayer d’afficher des trucs en dehors de la liste.
Bizarre, étant donné que le même code sans optimisations est tout à fait clean (même sous Valgrind).
Donc une boucle tout à fait correcte, deviens foireuse après optimisation. Hum…
Suite à cela, j’ai tenté de passer par gdb. Sans succès, étant donné que la variable d’intérêt, elt (c’est plus ou moins elle qui est utilisé dans la condition d’arrêt de la boucle), était optimized out (même en O1, et comme en O0 le bug ne se manifeste pas…).
Du coup, j’ai ajouté un petit printf des familles pour afficher elt et elt->node à chaque itération et j’ai pu constater que quand elt->node atteint la valeur nécessaire à l’arrêt de la boucle, et bien cette dernière continue son petit bonhomme de chemin >_<
N.B : Pourquoi elt et elt->node me direz-vous ?
Et bien parce que sont elles qui sont utilisées dans la condition d’arrêt du foreach (mais on va y revenir plus tard).
Bon, ma condition semble avoir disparue, c’est très ennuyeux ça. Je dois le vérifier.
Et pour cela, il n’y a qu’une seule façon viable : le code assembleur.
En avant donc. Jetons d’abord un coup d’œil au code généré par gcc (je commence par celui là, car je sais que même optimisé ma boucle semble toujours présente).
Donc voilà la sortie (je ne mets que la partie qui nous intéresse : la boucle) de gcc -g -O1 -S
.LVL4:
.loc 1 33 0
movq %rsp, %rdi
call slist_first
.LVL5:
leaq -8(%rax), %rdx
.LVL6:
movq %rax, %rbx
testq %rax, %rax
je .L2
.L3:
.loc 1 34 0 discriminator 2
movl 16(%rdx), %esi
movl (%rdx), %edx
.LVL7:
movl $.LC0, %edi
movl $0, %eax
call printf
.LVL8:
.loc 1 33 0 discriminator 2
movq %rbx, %rdi
call slist_next
.LVL9:
leaq -8(%rax), %rdx
.LVL10:
movq %rax, %rbx
testq %rax, %rax
jne .L3
Dans les dernières lignes on voit bien une comparaison (testq) suivi d’un saut conditionnel (jne) en début de boucle : tout va bien.
Voyons donc ce que clang -g -O1 -S va nous produire
.Ltmp13:
# BB#2:
leaq 8(%rsp), %rdi
.loc 1 33 0 # examples/min_bug.c:33:0
.Ltmp14:
callq slist_first
movq %rax, %rbx
.align 16, 0x90
.LBB0_3: # =>This Inner Loop Header: Depth=1
.loc 1 34 0 # examples/min_bug.c:34:0
movl -8(%rbx), %edx
movl 8(%rbx), %esi
movl $.L.str, %edi
xorb %al, %al
callq printf
.loc 1 33 0 # examples/min_bug.c:33:0
movq %rbx, %rdi
callq slist_next
movq %rax, %rbx
jmp .LBB0_3
Et là : BAM !!!
En plein dans le mille.
La dernière instruction de la boucle est un saut inconditionnel en début de boucle. Pas de cmp ou test. Mais un bon gros jmp tout ce qu’il y a de plus inconditionnel. La différence saute au yeux !
C’est donc une belle boucle infinie, ce qui explique que je continue à boucler même après avoir rencontrer ma condition d’arrêt (et donc ça explique également le fait que j’aille lire des addresses pourries qui me font faire une jolie Segmentation Fault).
* L’explication
Maintenant, on est sûr que le problème vient de la boucle, et donc de slist_foreach_elt.
Regardons donc ça de plus près :
#define slist_foreach_elt(list, curr, type, fieldname) \
for (curr = slist_elt(slist_first(list), type, fieldname); \
&(curr->fieldname) != NULL; \
curr = slist_elt(slist_next(&(curr->fieldname)), type, fieldname))
Hooooo yeaaah
Sachant que slist_elt est aussi une macro :
#define slist_elt(node, type, fieldname) \
((type*)((char*)(node) - offsetof(type, fieldname)))
Bon, ça peut faire peur de voir ça comme ça, à froid et sans explication
Mais en réalité c’est très simple, et c’est le cœur même de l’astuce qui permet d’avoir une liste générique sans passer par du void* (et oui, c’est standard et portable (si ça n’était pas portable, ça ne serait pas dans le noyau Linux).
Cela dit, je ne vais pas m’étendre sur le sujet du pourquoi du comment ça fonctionne (je pourrais donner des liens là dessus ou faire une explication dans un autre post si ça en intéresse certains).
Donc sans plus d’explications, je vous montre la ligne fautive (qui est belle est bien dans le foreach) :
&(curr->fieldname) != NULL; \
Ça peut sembler bizarre comme test, mais en fait ça tient la route (enfin presque, à un détail près et c’est bien pour cela que ça plante)
Pour information, voilà la version que l’on trouve dans le noyau Linux :
&pos->member != (head); \
C’est presque exactement le même code. À un détail près.
Détail qui vient du fait que la version Linux est une liste circulaire doublement chaînée. Et le point crucial ici est : circulaire.
Car par cette propriété, la condition d’arrêt est un test contre la tête de la liste, pas contre NULL.
Différence minime ? C’est ce que je pensais. Mais pas du tout, c’est une énorme différence : la différence entre un code correct et un UB.
* Explication pas à pas
Attention : cette explication contiendra peut-être des trucs moyennement clairs, ça sera sûrement dû au fait que je ne me sois pas attardé sur le principe de fonctionnement de cette liste.
Maintenant, allons-y.
Lorsque je suis sur le dernier nœud (dont le pointeur next vaut NULL), je vais exécuter cette ligne (la partie incrémentation du foreach) :
curr = slist_elt(slist_next(&(curr->fieldname)), type, fieldname))
Donc ici, je vais appeler slist_elt avec le contenu du pointeur next (car j’appelle slist_next), c’est à dire avec NULL.
Cela donne:
((type*)((char*)(node) - offsetof(type, fieldname)))
Donc dans le cas présent:
((pair_list*)((char*)(NULL) - offsetof(pair_list, node)))
offsetof renvoie, comme son nom l’indique, l’offset (en byte) d’un champ dans une structure.
Ici, je veux l’offset de mon nœud dans la structure. Ça me retourne donc 8 (en effet, dans la structure pair_list il y a un unsigned avant ce champ, et les unsigned sur ma machine font 4 bytes et il y a 4 bytes de padding pour aligner le champs suivant).
NULL étant défini, comme souvent (mais pas toujours), comme étant 0. Ça donne donc : 0 - 8.
NULL étant un pointeur, les pointeurs étant non signés et sur 64 bits (sur ma machine), on obtient l’addresse suivante : 0xfffffffffffffff8
Tiens, c’est l’adresse qui était apparue dans Valgrind ça
Donc après exécution de cette partie « incrémentation » de la boucle for, on retourne au début de la boucle et on va tester la condition :
&(curr->fieldname) != NULL; \
Ce qui donne dans mon cas :
&(elt->node) != NULL; \
curr vaut 0xfffffffffffffff8, l’offset du champ auquel je souhaite accéder est 8. Cela donne donc 0 quand je les additione. C’est bien égal à NULL, ma boucle s’arrête et c’est parfait.
Oui… Mais non !
Réfléchissons un peu. Qu’est ce que je fais dans cette condition :
&(elt->node)
Ici je récupère l’adresse d’un champ de ma structure. Très bien.
Et je le compare à NULL. Très b… errr. WTF!!!
C’est tout bonnement impossible !
NULL c’est 0, le début de la mémoire si je puis dire. Comment un champ en plein milieu de ma structure peut avoir comme addresse 0 ?
C’est impossible car cela voudrait donc dire que ma mémoire est circulaire et que ma variable est à cheval sur la fin de la mémoire (début de la structure à l’addresse 0xfffffffffffffff8) et sur le début (milieu de la structure à l’adresse 0).
Donc la seule façon valide pour que cette condition soit fausse est irréalisable (non, calculer un offset à partir d’un champ à l’adresse NULL ce n’est pas valide, et il est là mon UB).
Autrement dit, cette condition n’est jamais validé. Si elle n’est jamais validé, alors la boucle est infinie. Et ça, le compilateur le sait, donc quand il va optimiser (pourquoi faire un test quand on sait qu’il va toujours renvoyer vrai ?) il va tailler dans les rangs.
Il supprime donc le test durant la phase d’optimisation et on en arrive comportement que j’ai détecté.
* Solution
J’en vois deux pour le moment :
- implémenter ma liste en circulaire, du coup je testerai contre la tête et le problème sera résolu.
- créer un nœud global dont l’adresse me servira de valeur nulle, je ferai donc la comparaison contre lui et là aussi le problème sera résolu.
Je n’ai pas encore réfléchi à quelle solution je vais implémenter pour la simple et bonne raison que j’ai passé mon temps à rédiger ce post super long
* Conclusion
Tester son code avec plusieurs compilateurs, c’est le bien.
Le diable se cache dans les détails.
Rien n’est magique, tout est logique (même si pour remonter le fil, il faut parfois des connaissances en standard du C, optimisations faites par les compilo’ et lecture de codes assembleur ^^)
Oui, je sais il faudrait que je me refasse un blog
Hors ligne
#1753 Le 24/08/2013, à 22:14
- The Uploader
Re : /* Topic des codeurs [8] */
Niiiice.
Cela dit, je ne vais pas m’étendre sur le sujet du pourquoi du comment ça fonctionne (je pourrais donner des liens là dessus ou faire une explication dans un autre post si ça en intéresse certains).
o/
- Oldies PC : Intel Pentium 3 @ 800 Mhz sur CM ASUS P2B-F, GeForce 4 Ti4800 SE, Disque Dur Hitachi 160 Go, 512 Mo de RAM, 3DFX Voodoo 2, Sound Blaster 16 ISA PnP, Windows 98 SE / XP)
- Desktop : Intel Core i7 6700K @ 4 GHz sur CM ASUS Z170-P, GeForce GTX 1070, SSD Samsung 850 EVO 1 To, 16 Go de RAM, Disque Dur Seagate Barracuda 3 To, Windows 10
Hors ligne
#1754 Le 25/08/2013, à 02:07
- lukophron
Re : /* Topic des codeurs [8] */
Oui, je sais il faudrait que je me refasse un blog
+1
Le danger avec les glands est qu'ils prennent racines.
Corneille
Hors ligne
#1755 Le 25/08/2013, à 08:28
- Rolinh
Re : /* Topic des codeurs [8] */
Oui, je sais il faudrait que je me refasse un blog
Exactement ce que je me suis dit tout du long. Je peux te proposer deux choses si tu veux:
- un hébergement
- un billet invité sur mon blog
Mais ce magnifique post mérite plus de visibilité qu'un post parmi d'autres sur le topic des codeurs!
Hors ligne
#1756 Le 25/08/2013, à 10:42
- grim7reaper
Re : /* Topic des codeurs [8] */
* Solution
J’en vois deux pour le moment :
- implémenter ma liste en circulaire, du coup je testerai contre la tête et le problème sera résolu.
- créer un nœud global dont l’adresse me servira de valeur nulle, je ferai donc la comparaison contre lui et là aussi le problème sera résolu.
Je n’ai pas encore réfléchi à quelle solution je vais implémenter pour la simple et bonne raison que j’ai passé mon temps à rédiger ce post super long
Bon bah finalement, je suis passé par une troisième solution
Rendre la liste circulaire impliquait de rajouter un pointeur sur le dernier élément sous peine de doubler la complexité de certaines fonctions.
Ajouter un nœud statique en tant que valeur NULL polluait l’espace de nom et ça aurait pu donner des erreurs à la cons via les macro utilisées.
grim7reaper a écrit :Oui, je sais il faudrait que je me refasse un blog
Exactement ce que je me suis dit tout du long. Je peux te proposer deux choses si tu veux:
- un hébergement
- un billet invité sur mon blogMais ce magnifique post mérite plus de visibilité qu'un post parmi d'autres sur le topic des codeurs!
Un billet invité serait sûrement ce qu’il y a de plus simple.
D’un autre côté, comme je risque de refaire un post pour expliquer le fonctionnement de la liste (vu que The Uploader semble intéressé), autant avoir un espace hébergé pour le publier plutôt que le mettre ici. Ça permettrait aussi que je republie mon truc sur Valgrind (au lieu qu’il soit perdu sur sa page tout seul), voire mes anciens articles si je les retrouve (je dois avoir les sauvegardes pas loin normalement).
Donc à la limite oui, partir sur un hébergement serait peut-être le plus pratique.
À la base je pourrai m’auto-héberger comme avant, mais en ce moment c’est pas trop possible
Mon « serveur » est un vieux laptop super bruyant (et en plus je l’ai recyclé récemment), j’ai pas d’IP fixe (je dois passer par des trucs genre DNS dynamique, c’est sympa mais c’est pas le top non plus) et surtout dans quelques mois je vais bouger (et sûrement rebouger encore quelques mois après) donc bon niveau stabilité c’est pas ça. Je reconsidérerai l’auto-hébergenet une fois que ma situation géographique sera un peu plus stable.
En tout cas, merci ! C’est très sympa de ta part
Hors ligne
#1757 Le 25/08/2013, à 11:03
- Elzen
Re : /* Topic des codeurs [8] */
J'te file un domaine en .irlnc.org dès que t'es re-auto-hébergé si tu veux
Et je souscris au o/ de The Uploader ^^
Bravo, en tout cas
Elzen : polisson, polémiste, polymathe ! (ex-ArkSeth)
Un script pour améliorer quelques trucs du forum.
La joie de t'avoir connu surpasse la peine de t'avoir perdu…
timezone[blocklist]
Hors ligne
#1758 Le 25/08/2013, à 12:04
- Rolinh
Re : /* Topic des codeurs [8] */
Donc à la limite oui, partir sur un hébergement serait peut-être le plus pratique.
À la base je pourrai m’auto-héberger comme avant, mais en ce moment c’est pas trop possible
Mon « serveur » est un vieux laptop super bruyant (et en plus je l’ai recyclé récemment), j’ai pas d’IP fixe (je dois passer par des trucs genre DNS dynamique, c’est sympa mais c’est pas le top non plus) et surtout dans quelques mois je vais bouger (et sûrement rebouger encore quelques mois après) donc bon niveau stabilité c’est pas ça. Je reconsidérerai l’auto-hébergenet une fois que ma situation géographique sera un peu plus stable.
Vu la situation, ce qui peut être une solution sympa c'est un octopress ou similaire. Pour rappel, c'est du Ruby et il te génère le blog en statique à partir des tes sources (les pages et blog post peuvent être écrit avec $EDITOR en markdown). Et pour le déploiement, c'est un simple rake deploy qui te synchronise les fichiers statiques générés sur le serveur de destination. Le côté pratique, c'est surtout que le jour où tu veux te re-héberger, il suffit de changer l’adresse du serveur sur lequel synchronisé pour le tien et bingo. De plus, niveau backup c'est tout chez toi (et normalement géré via un dépôt git), moi je n'héberge que le html généré. Bon, le downside c'est que le site est purement statique et tu n'as pas accès aux commentaires par exemple (bien que des solutions existent).
En tout cas, merci ! C’est très sympa de ta part
Il n'y a pas de quoi.
J'te file un domaine en .irlnc.org dès que t'es re-auto-hébergé si tu veux
Pour aller dans le même sens, je peux te filer un domaine en .rolinh.ch ou .gw-computing.net.
En revanche, je te propose plutôt de te servir de DNS si tu achètes un nom de domaine (dans les 6-10€ par an donc jouable). Comme ça, pas de problèmes pour les références au blog, ça sera ton NDD et le jour ou tu t'auto-héberge à nouveau, je dirige mes DNS vers ta machine, ce qui est parfaitement transparent pour tes visiteurs.
Hors ligne
#1759 Le 25/08/2013, à 13:59
- grim7reaper
Re : /* Topic des codeurs [8] */
J'te file un domaine en .irlnc.org dès que t'es re-auto-hébergé si tu veux
Merci pour la proposition.
grim7reaper a écrit :Donc à la limite oui, partir sur un hébergement serait peut-être le plus pratique.
À la base je pourrai m’auto-héberger comme avant, mais en ce moment c’est pas trop possible
Mon « serveur » est un vieux laptop super bruyant (et en plus je l’ai recyclé récemment), j’ai pas d’IP fixe (je dois passer par des trucs genre DNS dynamique, c’est sympa mais c’est pas le top non plus) et surtout dans quelques mois je vais bouger (et sûrement rebouger encore quelques mois après) donc bon niveau stabilité c’est pas ça. Je reconsidérerai l’auto-hébergenet une fois que ma situation géographique sera un peu plus stable.Vu la situation, ce qui peut être une solution sympa c'est un octopress ou similaire. Pour rappel, c'est du Ruby et il te génère le blog en statique à partir des tes sources (les pages et blog post peuvent être écrit avec $EDITOR en markdown). Et pour le déploiement, c'est un simple rake deploy qui te synchronise les fichiers statiques générés sur le serveur de destination. Le côté pratique, c'est surtout que le jour où tu veux te re-héberger, il suffit de changer l’adresse du serveur sur lequel synchronisé pour le tien et bingo. De plus, niveau backup c'est tout chez toi (et normalement géré via un dépôt git), moi je n'héberge que le html généré. Bon, le downside c'est que le site est purement statique et tu n'as pas accès aux commentaires par exemple (bien que des solutions existent).
Oui, je pensais partir sur Octopress (j’avais vu que c’est ce que tu l’utilisais aussi).
Le statique ne me dérange pas le moins du monde.
Mon ancien site était tout ce qu’il y a de plus statique (et fait main !), idem pour l’absence de commentaires (de toutes façons, je pense qu’à part les gens d’ici, il ne va pas y avoir grand-monde qui va passer dessus ^^).
En plus, ce que tu décris semble bien plus facile et pérenne que ce que je faisais avant (avant en fait je ne voulais absolument pas de PHP sur mon serveur, et à l’époque mes connaissances en Python et Ruby était quasi-nulle donc du coup => 100% statique fait main).
Et puis au moins j’aurais un design un peu moins minimaliste :]
Petite question, tu utilises l’Octopress directement à partir du dépôt git ?
Elzen a écrit :J'te file un domaine en .irlnc.org dès que t'es re-auto-hébergé si tu veux
Pour aller dans le même sens, je peux te filer un domaine en .rolinh.ch ou .gw-computing.net.
En revanche, je te propose plutôt de te servir de DNS si tu achètes un nom de domaine (dans les 6-10€ par an donc jouable). Comme ça, pas de problèmes pour les références au blog, ça sera ton NDD et le jour ou tu t'auto-héberge à nouveau, je dirige mes DNS vers ta machine, ce qui est parfaitement transparent pour tes visiteurs.
Ok, je garde ça en tête pour quand je ferai l’acquisiton d’un NDD.
Hors ligne
#1760 Le 25/08/2013, à 15:10
- Rolinh
Re : /* Topic des codeurs [8] */
Petite question, tu utilises l’Octopress directement à partir du dépôt git ?
J'ai un dépôt git privé pour mon blog avec le dépôt octopress en remote afin de pouvoir faire les mises-à-jour.
Une mise-à-jour se limite à ça, dans le cas où tu utilises le thème d'origine tel quel:
git pull octopress master # Get the latest Octopress
bundle install # Keep gems updated
rake update_source # update the template's source
rake update_style # update the template's style
Ensuite, j'ai un deuxième dépôt git avec mon thème, que j'ai dérivé à partir de celui de base et je l'ai comme sous-module git dans le dossier .themes du dépôt de mon blog.
Hors ligne
#1761 Le 25/08/2013, à 17:54
- tshirtman
Re : /* Topic des codeurs [8] */
… Ou pelican, qui est comme octopress mais en python
\o
Sinon joli explication de bug, mais il reste un mystère si la condition est irréalisable, pourquoi la boucle s'arrête t'elle quand c'est pas optimisé? (j'ai peut être loupé la réponse dans l'explication ^^).
edit: après avoir relu le premier de tes articles cités, je crois comprendre, c'est l'un des exemples d'UB qu'il cite (impossibilité d'integer overflow).
Dernière modification par tshirtman (Le 25/08/2013, à 18:08)
Hors ligne
#1762 Le 25/08/2013, à 20:05
- grim7reaper
Re : /* Topic des codeurs [8] */
@Rolinh : Ok, Octopress est installé maintenant et j’ai commencé à regarder la conf’.
C’est super simple en fait
Je suis à ton écoute (plutôt par mail je suppose) pour la suite des opérations.
… Ou pelican, qui est comme octopress mais en python
Ça semble assez proche en effet.
Sinon joli explication de bug, mais il reste un mystère si la condition est irréalisable, pourquoi la boucle s'arrête t'elle quand c'est pas optimisé? (j'ai peut être loupé la réponse dans l'explication ^^).
En fait, dans l’absolu, la condition est réalisable.
C’est ce que j’explique dans le pas à pas et au final, j’obtiens bien &(curr->fieldname) == 0 (donc NULL).
Le truc, c’est que pour y arriver, je passe par un UB. Et quand on passe par un UB, le compilo est libre de faire ce qu’il veut.
Comme un programme C valide ne doit pas contenir d’UB, la stratégie de clang semble être (grossièrement, je pense que c’est plus fin que ça en réalité) de poser l’hyptothèse qu’il n’y a pas d’UB dans le code et de se servir de ça pour optimiser.
Du coup ça donne : une condition à besoin d’un UB pour être fausse + mon code n’a pas d’UB => la condition est toujours vrai => on peut la supprimer.
edit: après avoir relu le premier de tes articles cités, je crois comprendre, c'est l'un des exemples d'UB qu'il cite (impossibilité d'integer overflow).
Bien vu, mais raté. C’est bien un UB, mais pas celui-là.
En fait, hier je ne savais pas encore quel UB j’avais heurté. Sinon j’aurais bien entendu cité des bouts de la norme, comme à mon habitude
Mais je savais que c’était lié au pointeur NULL.
Bon là je viens de me replonger dans la norme et ça y est, j’ai mon chaînon manquant : mon UB.
Alors, d’abord la norme nous dit ça :
6.3.2.3 Pointers
[…]
3
An integer constant expression with the value 0, or such an expression cast to type
void *, is called a null pointer constant. If a null pointer constant is converted to a
pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal
to a pointer to any object or function.
[…]
Mais aussi :
6.5.6 Additive operators
[…]
7
For the purposes of these operators, a pointer to an object that is not an element of an
array behaves the same as a pointer to the first element of an array of length one with the
type of the object as its element type.8
When an expression that has integer type is added to or subtracted from a pointer, the
result has the type of the pointer operand. If the pointer operand points to an element of
an array object, and the array is large enough, the result points to an element offset from
the original element such that the difference of the subscripts of the resulting and original
array elements equals the integer expression. In other words, if the expression P points to
the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and
(P)-N (where N has the value n) point to, respectively, the i+n-th and i−n-th elements of
the array object, provided they exist. Moreover, if the expression P points to the last
element of an array object, the expression (P)+1 points one past the last element of the
array object, and if the expression Q points one past the last element of an array object,
the expression (Q)-1 points to the last element of the array object. If both the pointer
operand and the result point to elements of the same array object, or one past the last
element of the array object, the evaluation shall not produce an overflow; otherwise, the
behavior is undefined. If the result points one past the last element of the array object, it
shall not be used as the operand of a unary * operator that is evaluated.
[…]
Donc, l’opération fautive c’est NULL - 8 (dans la macro slist_elt).
Maintenant explication (ou du moins, une des interprétations possibles) :
- application de l’opérateur de soustraction sur un pointeur => la règle 6.5.6/7 s’applique donc NULL va donc se comporter comme un pointeur sur le premier élément d’un tableau de taille 1 (première ambiguïté : peut-être que la règle 6.3.2.3/3 empêche l’application de la règle 6.5.6/7 et que j’ai déjà mon UB ici, je vais supposer que non et continuer mon raisonnement).
- Ici, NULL pointe sur l’adresse la valeur 0, la valeur résultante est 0xfffffffffffffff8. Ils ne pointent donc clairement pas sur le même tableau (et l’adresse 0xfffffffffffffff8 n’est pas juste après 0 non plus…) donc selon la partie en gras de la règle 6.5.6/8 j’ai enfin mon UB.
De manière générale, toute opération arithmétique avec un pointeur NULL débouche sur un UB.
En effet, la condition 6.5.6/8 sera toujours violé (vu que la condition 6.3.2.3/3 garanti que NULL est différent de tout sauf de lui même, le résultat de l’opération ne pourra jamais pointer sur le même objet que NULL (vu que par définition il ne pointe sur rien)).
Même faire NULL - NULL c’est un UB. C’est presque le même principe que précédemment, sauf que c’est la règle 6.5.6/9 ou lieu de la 6.5.6/8 qui s’applique (je laisse les curieux consulter la norme).
Pour terminer sur une petite remarque, le C++ est un peu plus défini à ce niveau-là car il autorise deux opérations arithmétiques sur les pointeurs nuls :
- NULL + 0 ou NULL - 0 renvoie NULL
- NULL - NULL renvoie 0
Par contre, tout le reste c’est UB, comme en C.
Hors ligne
#1763 Le 25/08/2013, à 22:05
- Rolinh
Re : /* Topic des codeurs [8] */
@Rolinh : Ok, Octopress est installé maintenant et j’ai commencé à regarder la conf’.
C’est super simple en fait
Oui, c'est vraiment simple. Je ne pourrais plus utiliser un moteur de blog où je ne peux pas écrire avec $EDITOR pour ma part...
Je suis à ton écoute (plutôt par mail je suppose) pour la suite des opérations.
Effectivement. Mail envoyé.
Hors ligne
#1764 Le 25/08/2013, à 23:53
- The Uploader
Re : /* Topic des codeurs [8] */
@Elzen :
Et voilà les applications de Touhty, toutes les applications
Pour certaines j'ai dû créer un fichier .desktop
Pour d'autres, les dépendances n'étaient pas très claires.
Mais je verrais ça une autre fois
/dodo
- Oldies PC : Intel Pentium 3 @ 800 Mhz sur CM ASUS P2B-F, GeForce 4 Ti4800 SE, Disque Dur Hitachi 160 Go, 512 Mo de RAM, 3DFX Voodoo 2, Sound Blaster 16 ISA PnP, Windows 98 SE / XP)
- Desktop : Intel Core i7 6700K @ 4 GHz sur CM ASUS Z170-P, GeForce GTX 1070, SSD Samsung 850 EVO 1 To, 16 Go de RAM, Disque Dur Seagate Barracuda 3 To, Windows 10
Hors ligne
#1765 Le 26/08/2013, à 00:02
- tshirtman
Re : /* Topic des codeurs [8] */
Ok, c'est donc pas une histoire d’arithmétique en général, mais bien le cas particulier du pointeur NULL. Merci de l'explication… (oh et j'ai du mal a voir l'utilité de +0 et -0 en C++, mais j'imagine qu'il doit y en avoir ^^').
Mais là, en relisant ton explication, je me dis que j'aurais pas stocké la node elle même dans la structure, mais un pointeur, vers celle-ci, la comparaison avec NULL aurait alors du sens non? (le pense qu'on compare el->node pas &(el->node) du coup), Mais je ne comprends peut être pas bien l'intérêt global de la maneuvre, vu que tu dis que c'est pour éviter un pointer void*.
oh, et sinon, dans mes trucs bien plus haut niveau, je voulais dire que docopt c'est trop bien (c'est du python mais y'a un port ruby, lua et autres, j'imagine qu'un port C ferait des heureux, mais c'est surement une sacrée galère ^^). Au lieu de se casser la tête à définir le parsage des options, et a écrire l'usage, on écrit juste l'usage, et il le lit, et stock ce qui a été passé dans un dict, c'est juste génial, il fait les check et tout, et affiche l'usage si on demande un truc pas prévus.
Oh, et https://gist.github.com/tshirtman/6301662 (un exemple de rendu qui a fait souffler le mac a coté c'était pour tester par ce qu'il semble que j'ai un soucis driver sur ubuntu, ça crash quand je pousse trop le nombre de branches, avec des jolies messages comme ça dans dmesg).
Dernière modification par tshirtman (Le 26/08/2013, à 00:06)
Hors ligne
#1766 Le 26/08/2013, à 00:15
- Elzen
Re : /* Topic des codeurs [8] */
@The Uploader : bravo et merci
Juste, le lis :
elzshow-git 50.499d664-1 0 Elzen's picture-viewer and quickviewer
Tu as mis ElzShow et ElzView ensemble ?
Elzen : polisson, polémiste, polymathe ! (ex-ArkSeth)
Un script pour améliorer quelques trucs du forum.
La joie de t'avoir connu surpasse la peine de t'avoir perdu…
timezone[blocklist]
Hors ligne
#1767 Le 26/08/2013, à 04:00
- grim7reaper
Re : /* Topic des codeurs [8] */
Ok, c'est donc pas une histoire d’arithmétique en général, mais bien le cas particulier du pointeur NULL. Merci de l'explication… (oh et j'ai du mal a voir l'utilité de +0 et -0 en C++, mais j'imagine qu'il doit y en avoir ^^').
Andrew Koenig, un des grands monsieurs du C++ (le Koenig lookup du C++, ça vient de lui), avait publié un court article dans Dr. Dobb's à ce sujet justement.
Mais là, en relisant ton explication, je me dis que j'aurais pas stocké la node elle même dans la structure, mais un pointeur, vers celle-ci, la comparaison avec NULL aurait alors du sens non? (le pense qu'on compare el->node pas &(el->node) du coup), Mais je ne comprends peut être pas bien l'intérêt global de la maneuvre, vu que tu dis que c'est pour éviter un pointer void*.
Oui, la comparaison el->node contre NULL aurait du sens.
Cela dit, ça ajouterai un niveau d’indirection inutile et pas super instinctif. Parce que si je déclare ma structure comme étant :
typedef struct
{
unsigned to;
slist_node* node;
unsigned from;
} pair_list;
Sachant que l’unique champ de la structure slist_node n’est qu’en fait qu’un pointeur sur slist_node*, ça reviens à faire :
typedef struct
{
unsigned to;
slist_node** node;
unsigned from;
} pair_list;
Et là, on voit que c’est pas instinctif, car une liste chaîne on la déclare comme ça en générale :
typedef struct
{
/* Payload. */
node* next;
} node;
Pas avec un pointeur de pointeur.
oh, et sinon, dans mes trucs bien plus haut niveau, je voulais dire que docopt c'est trop bien (c'est du python mais y'a un port ruby, lua et autres, j'imagine qu'un port C ferait des heureux, mais c'est surement une sacrée galère ^^). Au lieu de se casser la tête à définir le parsage des options, et a écrire l'usage, on écrit juste l'usage, et il le lit, et stock ce qui a été passé dans un dict, c'est juste génial, il fait les check et tout, et affiche l'usage si on demande un truc pas prévus.
Ha ouais, sympa
Déjà que je trouvais que les modules standards de Ruby et Python pour gérer les options étaient plutôt bien foutu. Là on va encore un cran au dessus.
Oh, et https://gist.github.com/tshirtman/6301662 (un exemple de rendu qui a fait souffler le mac a coté c'était pour tester par ce qu'il semble que j'ai un soucis driver sur ubuntu, ça crash quand je pousse trop le nombre de branches, avec des jolies messages comme ça dans dmesg).
Ouch !
À priori ta carte graphique n’aime pas quand tu la pousses un peu ^^'
Hors ligne
#1768 Le 26/08/2013, à 05:01
- Pylades
Re : /* Topic des codeurs [8] */
Le C99 apporte des trucs pas mal, je ne dis pas (genre restrict, inline, bool, __func__, …).
Mais ça apporte aussi son lot de truc inutiles (les commentaires //) , voire pire : franchement foireux (oui, je pense aux VLA (pas pour rien qu‘ils sont passés en optionnel pour le C11)). Et ça n’est probablement pas étranger au fait que son support soit peu répandu, même 14 ans après sa sortie.
Ça me fait du bien que tu dises ça. ^^
“Any if-statement is a goto. As are all structured loops.
“And sometimes structure is good. When it’s good, you should use it.
“And sometimes structure is _bad_, and gets into the way, and using a goto is just much clearer.”
Linus Torvalds – 12 janvier 2003
Hors ligne
#1769 Le 26/08/2013, à 07:59
- grim7reaper
Re : /* Topic des codeurs [8] */
En fait, je crois que je me suis un peu trop pris la tête hier : rien qu‘avec la règle 6.3.2.3/3 le compilo’ peut détecter que ma condition ne sera jamais fausse.
Je compare l’adresse d’un champ de ma structure (donc l’adresse d’un objet) avec NULL (qui est ne peut pas être l’adresse d’un objet), partant de là le compilo sait que le test ne renverra jamais faux.
grim7reaper a écrit :Le C99 apporte des trucs pas mal, je ne dis pas (genre restrict, inline, bool, __func__, …).
Mais ça apporte aussi son lot de truc inutiles (les commentaires //) , voire pire : franchement foireux (oui, je pense aux VLA (pas pour rien qu‘ils sont passés en optionnel pour le C11)). Et ça n’est probablement pas étranger au fait que son support soit peu répandu, même 14 ans après sa sortie.Ça me fait du bien que tu dises ça. ^^
Tiens un revenant (enfin, sur ce topic, pas sur le forum en général)
Ouais, les VLA ça craint un max…
Si ta variable qui définie la taille de ton tableau contient une valeur un peu trop grande, bah tu vas te manger une stack overflow (du moins, dans la façon dont gcc (et clang?) les implémente) à l’exécution.
Et le plus beau dans tout ça, c‘est que même si tu veux poser une limite sur la taille de ton tableau pour éviter de faire péter la stack, bah ça ne sera pas portable vu que la taille de la stack dépend du système (bien que l‘on puisse la modifier via le linker je suppose).
Hors ligne
#1770 Le 26/08/2013, à 09:00
#1771 Le 26/08/2013, à 09:04
- Pylades
Re : /* Topic des codeurs [8] */
Je n’écris pas mais je continue d’essayer de lire.
“Any if-statement is a goto. As are all structured loops.
“And sometimes structure is good. When it’s good, you should use it.
“And sometimes structure is _bad_, and gets into the way, and using a goto is just much clearer.”
Linus Torvalds – 12 janvier 2003
Hors ligne
#1772 Le 26/08/2013, à 09:13
- The Uploader
Re : /* Topic des codeurs [8] */
@The Uploader : bravo et merci
Juste, le lis :
elzshow-git 50.499d664-1 0 Elzen's picture-viewer and quickviewer
Tu as mis ElzShow et ElzView ensemble ?
Euh non, j'ai juste oublié de différencier leurs deux PKGBUILD, du coup y'avait que l'un des deux, c'est corrigé.
Pour elzshow, le fait qu'il ouvre les images mais pas que m'a rappellé quickview de Windows 98, du coup j'ai essayé de le décrire ainsi (on peut pas faire un roman dans la ligne pkgdesc= ^^')
edit: j'ai mis à jour les dépendances optionnelles de touhy-git pour y inclure les applications, et j'ai corrigé un erreur dans celles de elzfold. ^^
Je m'attaque à touhy-meta (le méta paquet pour ceux qui veulent tout)
Aussi je me suis trompé au niveau des dépendances : aucune n'est sur l'AUR (ce qui est cool, ça veut dire que Touhy s'installe aussi vite que possible : c'est à dire sans passer par plus de compilations/packagings que nécessaire. )
* deux sont sur l'AUR mais se téléchargent/packagent très rapidement : python-xklavier, et python2-pyalsaaudio
Dernière modification par The Uploader (Le 26/08/2013, à 12:11)
- Oldies PC : Intel Pentium 3 @ 800 Mhz sur CM ASUS P2B-F, GeForce 4 Ti4800 SE, Disque Dur Hitachi 160 Go, 512 Mo de RAM, 3DFX Voodoo 2, Sound Blaster 16 ISA PnP, Windows 98 SE / XP)
- Desktop : Intel Core i7 6700K @ 4 GHz sur CM ASUS Z170-P, GeForce GTX 1070, SSD Samsung 850 EVO 1 To, 16 Go de RAM, Disque Dur Seagate Barracuda 3 To, Windows 10
Hors ligne
#1773 Le 26/08/2013, à 09:32
- Rolinh
Re : /* Topic des codeurs [8] */
j'imagine qu'un port C ferait des heureux
C'est prévu apparemment
Mais heu... "Step 3. Include the generated docopt.c into your program"
#include "docopt.c"
int main(int argc, char *argv[])
{
DocoptArgs args = docopt(argc, argv, /* help */ 1, /* version */ "2.0rc2");
printf("--help == %s\n", args.help ? "true" : "false");
printf("--version == %s\n", args.version ? "true" : "false");
printf("--tcp == %s\n", args.tcp ? "true" : "false");
printf("--serial == %s\n", args.serial ? "true" : "false");
printf("--host == %s\n", args.host);
printf("--port == %s\n", args.port);
printf("--timeout == %s\n", args.timeout);
printf("--baud == %s\n", args.baud);
}
La première ligne me fait un peu mal quand même...
Et à voir ici, il y a pas mal de ports (C, Haskell, Scala, PHP, .NET, Shell, CoffeeScript, Clojure, TCL, Rust et en plus de Ruby et Lua).
Hors ligne
#1774 Le 26/08/2013, à 09:39
- grim7reaper
Re : /* Topic des codeurs [8] */
Ha ouais, possibilité de format string attack.
Tiens d‘ailleurs, c‘est bizarre que tu aies ça dans ton code car :
- lors du débat sur scanf (quand tu es arrivé sur ce topic), les format string attack avait été évoqué en tant qu‘argument contre scanf (mais bon, par Revan26914, pas par toi), mais elles sont aussi valable pour printf quand on l’utilise mal (le premier argument c‘est le format, pas une chaîne à afficher)
- j’ai tendance à marteler (encore récemment) que quand on veut afficher une chaîne de caractères sans traitement, il faut utiliser puts/fputs et pas printf (qui comme son nom l’indique est pour des affichages formatés).
Donc tu n’as aucune excuse
D‘ailleurs, une preuve que pour afficher une chaîne simples il ne faut pas utiliser printf c‘est que, si le cas est vraiment évident, le compilateur va remplacer ton appel à printf (exemple ici).
Donc du coup, tu peux faire un nouveau patch pour remplacer tes printf par des fputs afin d‘avoir un code sémantiquement correct
Édit : dans ma ligne de compil‘ gcc j’ai l’option -Wformat=2 (clang accepte aussi cette option) qui permet de détecter ce genre de chose.
Dernière modification par grim7reaper (Le 26/08/2013, à 09:47)
Hors ligne
#1775 Le 26/08/2013, à 09:59
- Rolinh
Re : /* Topic des codeurs [8] */
J'étais sûr que j'allais avoir droit à ce discours.
Ce problème ne m'a pas sauté aux yeux à première vu (alors qu'il aurait dû). Je vais prendre comme excuse (parfaitement non-valable) que c'est mon #define pour gettext qui m'a obfusqué la vue (#define _(STRING) gettext(STRING)).
Oui, je sais plusieurs personnes ici martèlent puts/fputs au lieu de printf car c'est sémantiquement plus correct, toussa. Sur le principe, je suis parfaitement d'accord: si on a affaire à une chaîne non-formatée, inutile d'utiliser printf. Mais je n'aime pas puts pour une raison de confort personnel: une certaine inconsistance.
Explication:
int printf(const char *format, ...)
int fprintf(FILE *stream, const char *format, ...)
int puts(const char *s)
int fputs(const char *s, FILE *stream)
Pourquoi, dans le cas de fputs (putc/fputc aussi d'ailleurs), mettre le stream en deuxième argument alors que c'est l'inverse pour fprintf? Bon, c'est vrai que fgets aussi prend le stream en dernier argument n'empêche que ça chamboule mon petit confort.
Et aussi, pourquoi fputs n'écrit pas le \0 alors que puts ajoute la trailing new line? Je trouve ça aussi un peu idiot et inconsistant.
Dernière modification par Rolinh (Le 26/08/2013, à 10:00)
Hors ligne