2 votes

Utiliser une fonction avec une référence comme une fonction avec des pointeurs ?

Aujourd'hui, je suis tombé sur un morceau de code qui m'a semblé horrifiant. Les morceaux étaient éparpillés dans différents fichiers, j'ai essayé d'écrire le coeur de celui-ci dans un cas de test simple ci-dessous. La base de code est régulièrement analysée avec FlexeLint sur une base quotidienne, mais cette structure est restée dans le code depuis 2004.

Le truc, c'est qu'une fonction implémentée avec un passage de paramètre utilisant des références est appelée comme une fonction avec un passage de paramètre utilisant des pointeurs... en raison d'une conversion de fonction. La structure a fonctionné depuis 2004 sur Irix et maintenant lors de son portage, cela fonctionne effectivement aussi sur Linux/gcc.

Maintenant, ma question. Peut-on faire confiance à cette structure? Je peux comprendre si les constructeurs de compilateurs implémentent le passage par référence comme s'il s'agissait d'un pointeur, mais est-ce fiable? Y a-t-il des risques cachés?

Devrais-je changer le fref(..) pour utiliser des pointeurs et risquer de casser quelque chose dans le processus?

Qu'en pensez-vous?

Edit

Dans le code réel, à la fois fptr(..) et fref(..) utilisent la même struct - le code modifié ci-dessous reflète cela mieux.

#include 
#include 

using namespace std;

// ----------------------------------------
// Ceci sera passé en référence dans fref(..)

struct string_struct {
    char str[256];
};

// ----------------------------------------
// Utilisation de pointeur ici!

void fptr(string_struct *str) 
{
    cout << "fptr: " << str->str << endl;
}

// ----------------------------------------
// Utilisation de référence ici!

void fref(string_struct &str) 
{
    cout << "fref: " << str.str << endl;
}

// ----------------------------------------
// Cast vers f(const char*) et appel avec un pointeur

void ftest(void (*fin)()) 
{
    string_struct str;
    void (*fcall)(void*) = (void(*)(void*))fin;
    strcpy(str.str, "Bonjour!");
    fcall(&str);
}

// ----------------------------------------
// Passons au test

int main() {
    ftest((void (*)())fptr); // test avec fptr qui utilise un pointeur
    ftest((void (*)())fref); // test avec fref qui utilise une référence
    return 0;
}

3voto

sbi Points 100828

Que pensez-vous?

Nettoyez le. C'est un comportement indéfini et donc une bombe qui pourrait exploser à tout moment. Une nouvelle plate-forme ou version de compilateur (ou une phase lunaire, pour être précis) pourrait la déclencher.

Bien sûr, je ne sais pas à quoi ressemble le vrai code, mais d'après votre version simplifiée, il semble que la manière la plus simple serait de donner à string_struct un constructeur implicite prenant un const char*, de templatize ftest() sur l'argument du pointeur de fonction, et de supprimer tous les castings impliqués.

2voto

janks Points 1291

Il s'agit évidemment d'une technique horrible, et formellement c'est un comportement indéfini et une erreur grave d'appeler une fonction à travers un type incompatible, mais cela devrait "fonctionner" en pratique sur un système normal.

Au niveau machine, une référence et un pointeur ont exactement la même représentation; ce ne sont que l'adresse de quelque chose. Je m'attends pleinement à ce que fptr et fref se compilent exactement de la même manière, instruction par instruction, sur n'importe quel ordinateur que vous pourriez obtenir. Une référence dans ce contexte peut simplement être considérée comme du sucre syntaxique; un pointeur qui est automatiquement déréférencé pour vous. Au niveau machine, ils sont exactement les mêmes. Il pourrait bien sûr exister des plateformes obscures et/ou obsolètes où cela n'est pas le cas, mais généralement c'est vrai 99% du temps.

De plus, sur la plupart des plateformes courantes, tous les pointeurs d'objets ont la même représentation, tout comme tous les pointeurs de fonctions. Ce que vous avez fait n'est pas vraiment si différent d'appeler une fonction attendant un int à travers un type prenant un long, sur une plateforme où ces types ont la même largeur. C'est formellement illégal, et presque garanti de fonctionner.

On peut même en déduire de la définition de malloc que tous les pointeurs d'objets ont la même représentation; je peux allouer une énorme quantité de mémoire avec malloc, et y mettre n'importe quel objet (de style C) que je veux. Puisque malloc ne retourne qu'une seule valeur, mais que cette mémoire peut être réutilisée pour n'importe quel type d'objet que je veux, il est difficile de voir comment différents pointeurs d'objets pourraient raisonnablement utiliser des représentations différentes, à moins que le compilateur ne maintienne un grand ensemble de mappages de représentation de valeur pour chaque type possible.

void *p = malloc(100000);
foo  *f =  (foo*)p;  *f = some_foo;  
bar  *b =  (bar*)p;  *b = some_bar;
baz  *z =  (baz*)p;  *z = some_baz; 
quux *q =  (quux*)p; *q = some_quux;  

(Les casts laids sont nécessaires en C++). Ce qui précède est nécessaire pour fonctionner. Donc même si je ne pense pas qu'il soit formellement exigé qu'ensuite memcmp(f, b) == memcmp(z, q) == memcmp(f, q) == 0, il est difficile d'imaginer une implémentation saine qui pourrait rendre cela faux.

Cela dit, ne faites pas ça!

1voto

Ernelli Points 2133

Cela fonctionne par pur hasard.

fptr attend un const char * tandis que fref attend un string_struct &.

La structure string_struct a la même disposition mémoire que le const char * car elle ne contient qu'un tableau de caractères de 256 octets, et ne contient pas de membres virtuels.

En c++, l'appel par référence par exemple string_struct & est implémenté en passant un pointeur caché vers la référence, de sorte qu'il sera sur la pile d'appel comme s'il était passé comme un vrai pointeur.

Mais si la structure string_struct change, tout va se casser, donc le code n'est pas considéré comme sûr du tout. De plus, il dépend de l'implémentation du compilateur.

1voto

Eddy Pronk Points 3084

Convenons simplement que c'est très moche et que vous allez changer ce code. Avec la conversion que vous promettez, assurez-vous que les types correspondent et ce n'est clairement pas le cas. Au moins, débarrassez-vous de la conversion de style C.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X