Typedefs incohérents en C

Le hasard est parfois marrant. Il n’y a pas si longtemps, j’ai passé plusieurs jours à pister une erreur mystérieuse dans du code C. Le symptôme était plutôt simple (une fois qu’on y va en mode debug en regardant les variables qui vont bien) : les champs d’une structure ont des valeurs données dans une fonction, on passe la structure à une autre fonction, et la structure se retrouve avec les valeurs de ces champs décalées. En regardant le code assembleur (ça sert des fois), on pouvait constater que l’accès à un champ de la structure se faisait avec un offset de 0x44 dans une fonction et avec un offset de 0x40 dans l’autre. Il y avait donc une incohérence de définition des structures entre différents composants. La difficulté a été de trouver où, mais ce n’est pas exactement le sujet de l’article…

Là où ça devient « marrant », c’est que peu après avoir réglé ce problème, je suis tombé sur du code écrit pour faire un article sur ce blog et dont le sujet était justement l’incohérence de définition d’une structure entre deux fichiers C. Il est sans doute temps de reprendre ce code et de rédiger un article ^^

En général, quand plusieurs fichiers sources utilisent le même type de structure, on crée un fichier d’en-tête avec le typedef et on inclue cette en-tête dans les différents fichiers sources qui en ont besoin. Pour diverses raisons, on peut se retrouver avec des fichiers objets qui ont été compilés avec différentes définitions de la structure. Il se peut que la présence de certains champs dépende d’une compilation conditionnelle, qu’il existe plusieurs versions du fichier d’en-tête, ou encore parce que la structure contient d’autres types qui souffrent d’une incohérence.

Pour vous montrer l’effet d’une incohérence de structures, j’ai fait simple et j’ai utilisé une structure dont certains champs peuvent être « enlever » par le pré-processeur. Ce type de structure est définie dans definition.h :

#ifndef __DEFINITION_H
#define __DEFINITION_H

typedef struct
{
#ifdef MAKE_TYPE_COHERENT
	long number;
#endif
	void (*function)(void);
} type_t;
#endif

Il y a ensuite deux fichiers sources qui utilisent ce type de structure.

util.c

#include <stdio.h>

#define MAKE_TYPE_COHERENT
#include "definition.h"

void another_function(void)
{
	printf("I am '%s'\n", __func__);
}

void print(type_t t)
{
	puts("----------------");
	printf("number = %ld\n", t.number);
	printf("function = %p\n", t.function);
	puts("----------------");
}

type_t get(void)
{
	return (type_t) {666, another_function};
}

main.c

#include <stdio.h>

#include "definition.h"

extern void another_function(void);
extern void print(type_t t);
extern type_t get(void);

void one_function(void)
{
	printf("I am '%s'\n", __func__);
}

int main(void)
{
	printf("'one_function' is at @%p\n", one_function);
	printf("'another_function' is at @%p\n", another_function);

	type_t t = {0} ;
	t.function = one_function;
	print(t);
	t.function();

	t = get();
	print(t);
	printf("Calling function at %p...\n", t.function);
	t.function();

	return 0;
}

Je compile mon code avec la commande c99, qui rappelle clang sur mon Mac :

$ clang --version
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.3.0
Thread model: posix

Si la macro qui va bien n’est définie pas dans main.c, on obtient le résultat suivant lors de l’exécution du programme :

$ c99 -c util.c && c99 -c main.c && c99 *.o && ./a.out 
'one_function' is at @0x109941ce0
'another_function' is at @0x109941dd0
----------------
number = 4455668960
function = 0x0
----------------
I am 'one_function'
----------------
number = 666
function = 0x70000000700
----------------
Calling function at 0x29a...
Segmentation fault: 11

Et si cette macro est définie dans main.c :

$ c99 -c util.c && c99 -c main.c -DMAKE_TYPE_COHERENT && c99 *.o && ./a.out 
'one_function' is at @0x10bd52cd0
'another_function' is at @0x10bd52dd0
----------------
number = 0
function = 0x10bd52cd0
----------------
I am 'one_function'
----------------
number = 666
function = 0x10bd52dd0
----------------
Calling function at 0x10bd52dd0...
I am 'another_function'
Publicités

Une Réponse

  1. Pingback: Vérification de types à l’édition des liens | Pierre Gradot

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s