IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Différence entre (uint32_t)tableau[0] et *(uint32_t*)&tableau[0] ?


Sujet :

Langage C++

  1. #1
    Membre à l'essai
    Inscrit en
    Mai 2012
    Messages
    18
    Détails du profil
    Informations forums :
    Inscription : Mai 2012
    Messages : 18
    Points : 12
    Points
    12
    Par défaut Différence entre (uint32_t)tableau[0] et *(uint32_t*)&tableau[0] ?
    Bonjour,

    dans le cadre du développement d'une appli, je suis tombé sur une ligne de code un peu étrange qui a un comportement encore plus bizarre que ce à quoi elle ressemble :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    return *(uint32_t*)&tableau[0];
    Je m'explique : tableau est (comme son nom l'indique) un tableau d'uint8_t. La fonction d'où est tirée la ligne ci-dessus est supposée récupérer la valeur du premier uint8_t de ce tableau puis la caster en uint32_t.

    Cependant (ce n'est pas moi qui ait écrit ce code), il me semblait plus simple de procéder comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    return (uint32_t)tableau[0];
    cad utiliser directement la valeur plutôt que de caster en pointeur puis déréférencer. Cependant, ces deux lignes ne renvoient pas la même valeur !

    J'ai alors essayé une troisième forme, pour le test :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    return *(uint32_t*)tableau;
    Cette dernière renvoie les mêmes valeurs que la première forme mais je n'arrive pas à saisir en quoi la seconde forme est différente des deux autres... Quelqu'un peut-il m'éclairer ?

    D'avance, merci !

    Fointard

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Autant le dire tout de suite, les trois codes sont particulièrement crades, ne serait-ce parce qu'il transtypent ("castent") ton tableau de manière sauvage.

    Le principe pour savoir ce que fait ce genre de code est de partir de la droite et de "remonter" le code vers la gauche.

    Ainsi, le premier code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    return *(uint32_t*)&tableau[0];
    va prendre l'élément 0 du tableau et en récupérer l'adresse (ce qui correspond, simplement à l'adresse représentée par tableau ), convertir "à la barbare" ce pointeur en pointeur sur un uint32_t et renvoyer "ce qui est pointé par ce pointeur

    Le deuxième code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    return (uint32_t)tableau[0];
    va récupérer le premier élément du tableau et renvoyer le résultat de sa conversion "barbare" en uint32_t.

    Enfin, le troisième code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    return *(uint32_t*)tableau;
    va prendre la valeur de tableau (qui correspond à l'adresse de son premier élément, le convertir en un pointeur sur uint32_t et renvoyer ce qui est pointé par cette adresse, ce qui correspond, finalement, parfaitement au premier code

    Le fait est qu'un pointeur restera un pointeur quel que soit le type pointé: un pointeur représente l'adresse mémoire (codée sur 32 ou sur 64 bits selon l'architecture, enfin, pour faire simple) à laquelle on trouve un élément du type donné.

    Lorsque tu prends l'adresse d'un élément d'un type donné (mettons: du type char), et que tu dis au compilateur de la considérer comme si c'était l'adresse d'un élément d'un autre type (mettons uint32_t), l'accès à "ce qui se trouve à cette adresse" te donnera accès à l'intégralité de l'espace mémoire nécessaire pour relrésenter le type que tu as indiqué.

    Autrement dit, le fait de renvoyer "ce qui est pointé" par le résultat de la conversion de tableau en pointeur vers un uint32_t correspondra au contenu des 4 char successifs qui permettent d'obtenir les 32 bits nécessaires à la représentation d'un uint32_t.

    Par contre, dans les même circonstances (donc tableau est un tableau de char), si tu converti un élément du tableau en uint32_t, tu obtiendra simplement un uint32_t dont la valeur correspond à celle du char indiqué (et donc, dont les 3 premiers bytes valent simplement 0).

    Je m'explique:
    imaginons que tableau soit du type de
    et que les valeurs respectives soient
    • 0x11 pour tableau[0]
    • 0x22 pour tableau[1]
    • 0x33 pour tableau[2]
    • 0x44 pour tableau[3]
    Si tu converti "simplement" tableau[0] en uint32_t, la conversion va se "contenter" de rajouter 3 bytes de valeur nulle, pour faire en sorte que la valeur renvoyée soit identique à la valeur d'origine.

    La valeur renvoyée sera donc de 0x00000011. La conversion agira alors comme une "simple promotion" de ton char en int.

    Par contre, si tu passes par les adresses, et que tu convertir l'adresse de tableau[0] (qui correspond à la valeur de tableau), qui est sensée être de type char *, en uint32_t * puis que tu prends ce qui se trouve à cette adresse, la valeur renvoyée va correspondre aux 4 bytes (selon l'exemple) nécessaires à la représentation d'un uint32_t, c'est à dire 0x11223344.

    C'est, d'une certaine manière, mais sans les limitations qui leur sont propres, fort similaire au comportement que l'on pourrait observer si l'on travaillait avec une union du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    union UnNom{
    char tableau[4];
    uint32_t in32Bits;
    };
    Après, il y aura le problème de boutisme éventuel au moment d'évaluer la valeur réelle que cela représente, mais c'est un autre problème
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre à l'essai
    Inscrit en
    Mai 2012
    Messages
    18
    Détails du profil
    Informations forums :
    Inscription : Mai 2012
    Messages : 18
    Points : 12
    Points
    12
    Par défaut
    Okay, je crois que j'ai compris, merci beaucoup !

    Pour résumer, dans les formes 1 et 3, 3 des 4 bytes renvoyés sont piochés à un endroit de la mémoire auquel on ne devrait pas les piocher, tandis que la forme 2 procède plutôt à un "ajout de zéros" devant la valeur de l'uint8_t afin que ce dernier tienne finalement sur 32 bits plutôt que sur 8 tout en gardant la même valeur.

    En fait, c'est bien ce que je pensais ! Le forme 2 est donc la plus correcte... Ou en tout cas, la moins incorrecte. Par quoi est-ce que je remplace le transtypage dégueu pour que ça ait l'air un peu plus propre (et surtout que ce soit plus rapide !) ?

    Merci encore !

    Fointard

  4. #4
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Fointard Voir le message
    Okay, je crois que j'ai compris, merci beaucoup !

    Pour résumer, dans les formes 1 et 3, 3 des 4 bytes renvoyés sont piochés à un endroit de la mémoire auquel on ne devrait pas les piocher, tandis que la forme 2 procède plutôt à un "ajout de zéros" devant la valeur de l'uint8_t afin que ce dernier tienne finalement sur 32 bits plutôt que sur 8 tout en gardant la même valeur.

    En fait, c'est bien ce que je pensais ! Le forme 2 est donc la plus correcte... Ou en tout cas, la moins incorrecte.
    Cela dépend vraiment du but recherché.

    Si l'idée est d'avoir un int (ou unsigned int) par caractère, alors, c'est la solution 2 qui doit être envisagée.

    Si l'idée est d'avoir un int pour quatre char (parce que la valeur est sensée être un int, mais qu'on a tout chargé dans un tableau de caractères) alors, ce sont les solution 1 et 3 qui sont d'application.
    Par quoi est-ce que je remplace le transtypage dégueu pour que ça ait l'air un peu plus propre (et surtout que ce soit plus rapide !) ?
    Si, dans ta situation, c'est la solution 2 qui t'intéresse, tu devrais carrément pouvoir laisser faire le compilateur, qui fera la promotion du char en int.

    Au pire, tu devrais utiliser le reinterpret_cast qui est préférable à un transtypage C style tout en ayant un comportement exactement similaire
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre à l'essai
    Inscrit en
    Mai 2012
    Messages
    18
    Détails du profil
    Informations forums :
    Inscription : Mai 2012
    Messages : 18
    Points : 12
    Points
    12
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Si l'idée est d'avoir un int (ou unsigned int) par caractère, alors, c'est la solution 2 qui doit être envisagée.

    Si l'idée est d'avoir un int pour quatre char (parce que la valeur est sensée être un int, mais qu'on a tout chargé dans un tableau de caractères) alors, ce sont les solution 1 et 3 qui sont d'application.
    Là, j'avoue ne pas être sûr d'avoir suivi. Dans le premier cas que tu décris, la valeur que l'on souhaite récupérer serait codée sur 8 bits, tandis que dans le second elle serait codée sur 32 bits mais divisée en 4 parties (dans des uint8_t), c'est bien ça ?

    Si c'est le cas, sachant je ne souhaite récupérer que la valeur du premier uint8_t (qui contient en fait 4 valeurs, chacune codée sur 2 bits), je me trouve donc bien dans le cas de la solution 2, on est d'accord ?

  6. #6
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Je vais essayer de te donner un exemple qui, je l'espère, te permettra de comprendre.

    Imaginons que tu aies une structure (vraiment très orientée C ) personne sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct Personne{
        char nom[10];
        char prenom[10];
        int day; // ces trois int représentent la date de naissance
        int mount;
        int year;
        // et ce qui suit représente l'adresse
        char rue[10];
        int numero;
        int CP 
        char ville[10];
    };
    Si l'on ne tient pas compte des phénomènes d'alignement que l'on risque de rencontrer entre prenom et day ainsi qu'entre rue et numero, c'est une structure qui aura une taille (si j'ai bien compté) de 60 bytes (en fait, sa taille réelle a de bonnes chances d'être de 68 bytes à cause des alignements sus-cités )

    La norme garanti que je peux convertir cette structure dont la taille est de 60 bytes en un tableau de 60 caractères, pui re convertir ce tableau de 60 caractères en un objet de type personne sans aucune perte de donnée.

    C'est ce que l'on appelle un type POD (pour Plain Old Data).

    Si j'ai un tableau de Personnes (mettons, de 10 personnes), je peux donc faire le faire passer pour un tableau de (10 * 60 = 600) caractères, par exemple, pour le sérialiser dans un fichier "binaire":
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main(){
        Personne tab[10];
        std::ofstream ofs("fichier.bin",std::ios::binary);
        ofs.write(tab, 10*sizeof(Personne));
        /* ou ou ou */
       ofs.write(reinterpret_cast<const char*>(&value), sizeof(Personne));
    }
    Et, de même, je peux charger l'intégralité du fichier sous la forme d'un tableau de caractères (cf cette entrée de la FAQ)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int main(){
        char tab[10*sizeof(Personne)];
        std::ifstream ifs("fichier.bin",std::ios::binary);
        ofs.read(tab, 10*sizeof(Personne));
    }
    Le problème, c'est que j'aurai alors un tabeau de (600) char, qui correspondront à 10 éléments de 60 bytes qui sont répartis sous la forme de
    • deux tableaux de 10 char succesifs (pour nom et prenom)
    • quatre char (c'est la taille d'un int) correspondant à day
    • quatre char (c'est la taille d'un int) correspondant à mount
    • quatre char (c'est la taille d'un int) correspondant à year
    • un tableau de 10 char successif correspondant à rue
    • quatre char (c'est la taille d'un int) correspondant à numero
    • quatre char (c'est la taille d'un int) correspondant à CP
    • un tableau de 10 char successifs correspondant à ville
    (j'ignore, encore une fois, le phénomène d'alignement des données pour des raisons de facilité d'explication ).

    Je pourrais, évidemment, utiliser les méthodes 1 et 3 que tu présentes à l'origine pour transformer ce tableau de 600 caractères en un tableau de 10 Personnes.

    Mais imagines que, pour des besoin de la cause (sans doute un problème d'interface de la fonction), tu te retrouves avec une fonction qui manipule un char * dans laquelle tu voudrais récupérer l'année de naissance des dix personnes.

    Tu est parfaitement en mesure de déterminer l'indice du premier caractère à utiliser pour l'année: nom utilise les caractères de 0 à 9 inclus, prenom commence à 10 et s'arrête à 19, day commence à 20 et s'arrète à 23, mount commence à 24 et s'arrête à 27 et enfin, year commence à l'indice 28 et s'arrête à l'indice 31.

    Seulement, d'ici à l'année 65535, toutes les années pourront être codées sur un maximum de 2 bytes (16 bits)

    Et, manque de bol, il y a de grandes chances (selon le boutisme de la machine) que les deux bytes qui nous intéressent soient... les bytes indexés 30 et 31 (re ).

    L'année 2013, par exemple, sera codée sous la forme de 00 00 07 DD.

    Si tu essayes de la récupérer sous la forme numéro 2, tu auras quatre int avec des valeur respectives de 0x00000000, 0x00000000, 0x00000007 et 0x000000DD ce qui ne t'arrangera absolument pas.

    Il faut donc bel et bien que tu utilises les méthodes 1 ou 3 pour obtenir un résultat correct:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void foo( char *tab){
        for(int i=0;i<10;++i){
            int annee= *reinterpret_cast<int*>(&tab[i*60+28]);
            std::cout<<"annee de naissance de l'element "<<i<<":"<<annee
                  <<std::endl;
        }
    }
    C'est pour cela que je te dis que la solution que tu devras utiliser dépendra surtout de l'objectif que tu espères atteindre , car, si tu veux "uniquement" récupérer une valeur codée "à l'orignie" sur un byte (sur l'équivalent d'un char), alors, c'est effectivement la deuxième solution qu'il faut employer
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 3
    Dernier message: 22/03/2013, 21h02
  2. Réponses: 3
    Dernier message: 22/06/2011, 10h20
  3. [XL-2007] Différence entre consolidation, tableau croisé et liaison de feuilles
    Par guy2004 dans le forum Conception
    Réponses: 0
    Dernier message: 24/03/2011, 13h48
  4. Différences entre liste et tableau?
    Par fayred dans le forum Langage
    Réponses: 4
    Dernier message: 17/07/2008, 13h24
  5. Réponses: 2
    Dernier message: 27/05/2008, 09h56

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo