Partage
  • Partager sur Facebook
  • Partager sur Twitter

Vector en C

Sujet résolu
    5 avril 2021 à 18:33:16

    Bonsoir à tous !

    Je suis plutôt débutant en C et pour m'entraîner j'ai décidé de faire une struct Vector avec différentes fonctions d'ajout d'élément, d'affichage etc... Sauf que, évidemment, j'ai une erreur de segmentation depuis un petit bout de temps et je ne comprends vraiment pas pourquoi. Dans mon fichier Vector.h je crée la structure avec un entier pour la taille et un pointeur sur un entier pour le tableau. La fonction creerVect seule me renvoie pas d'erreur à l'execution mais la fonction afficherVect oui, savez-vous pourquoi ? (Les autres essais de fonctions sont en commentaire car je ne veux pas m'embrouiller avec pour l'instant).

    Voici le fichier Vector.h :

    #include <stdio.h>
    #include <stdlib.h>
    
    
    typedef struct Vector Vector;
    struct Vector{
      int taille;
      int* Vect;
    };
    
    void creerVect(Vector V){
      V.Vect = (int*) malloc(sizeof(int)*(V.taille));
      
      for(int i = 0; i<(V.taille); i++){
        *(V.Vect+i)=0;
      }
    }
    
    void supprEle(Vector V){
      V.Vect = (int*) realloc(V.Vect, sizeof(int)*(--(V.taille)));
    }
    
    void ajoutEle(Vector V, int e){
      
      if (V.Vect[V.taille] == 0){
        V.Vect = (int*) realloc(V.Vect, sizeof(int) * ((V.taille)*2));
        V.Vect[V.taille]=e;
      }
      else
        V.Vect[V.taille]=e;
    }
    
    
    void afficherVect(Vector V){
    
      if (V.taille > 0){
        printf("[%d",*(V.Vect));
        for(int i=1; i<(V.taille); i++){
          printf(", %d", *(V.Vect+i));
        }
        printf("]");
      }
      else{
        printf("Vector empty :(\n");
      }
    }
    



    Et le fichier Vector.c :

    #include <stdio.h>
    #include <stdlib.h>
    #include "Vector.h"
    
    
    int main(void){
    
      Vector V = {2, NULL};
      creerVect(V);
      afficherVect(V);
      printf("La taille est : %d\n", V.taille);
      /*
      ajoutEle(V, 1);
      printf("La taille est : %d", V.taille);
      ajoutEle(V, 2);
      printf("La taille est : %d", V.taille);
      afficherVect(V);
      supprEle(V);
      printf("La taille est : %d", V.taille);
      afficherVect(V);
      */
    
      return 0;
    }
      




    -
    Edité par EjeXjdk 5 avril 2021 à 18:41:37

    • Partager sur Facebook
    • Partager sur Twitter
      5 avril 2021 à 19:06:09

      Hello,

      ton approche est complètement erronée. 

      Déjà d'un point de vue C, le code 

      typedef struct Vector Vector;
      struct Vector{
        int taille;
        int* Vect;
      };
       
      void creerVect(Vector V){
        V.Vect = (int*) malloc(sizeof(int)*(V.taille));
         
        for(int i = 0; i<(V.taille); i++){
          V.Vect[i]=0;
        }
      }

      comme Vect est une structure, tu la crées en déclarant une variable de type. Si tu l'envoie comme paramètre à une fonction, ce sera un objet temporaire qui sera modifié donc → ça ne sert à rien. Tu accèdes à V.taille sans pour autant que ce champs ait une valeur cohérente.

      Bref … l'idée du Vector à la C++ est mal retranscrite.

      Si on commence par analyser ce qu'est un vecteur, on s'aperçoit qu'un vecteur est une collection ordonnée d'objets qui a une certaine capacité (il peut contenir jusqu'à C objets), une taille (il en contient à un moment donné T) et un conteneur qui peut contenir au moins C objets. Souvent les implémentations prennent en compte un agrandissement à la demande lorsque c'est nécessaire.

      Donc une implémentation qui pourrait convenir pourrait être :

      typedef struct Vector t_vector;
      struct Vector {
          size_t capacity;
          size_t size;
          int *data;
      };

      Dans une implémentation possible, tu pourrais donner une capacité par défaut pour initialiser correctement cette structure →

      #define INITIAL_CAPACITY ( (size_t) (1024) )
      
      t_vector *vector_new(void);
      
      t_vector *vector_new(void)
      {
          t_vector *new=malloc( sizeof *new );
          if (new) {
              new->capacity = INITIAL_CAPACITY;
              new->size = 0;
              new->data = malloc( new->capacity * sizeof *new->data );
              if (new->data) {
                  // cool tout c'est bien passé !
                  return new;
              } else {
                  // pb lors de l'allocation du conteneur
                  // faut gérer
                  free(new);
                  < là on gère suivant les conventions >
              }
          } else {
              // pb d'allocation dès le départ
              // faut gérer aussi selon les conventions
              ....
          }
      }

       Évidemment il faut encore écrire toutes les autres primitives …

      • Partager sur Facebook
      • Partager sur Twitter
        5 avril 2021 à 19:17:49

        Il y a beaucoup de chose qui ne vont pas, mais la principale est que le Vector de tes fonctions n'est qu'une copie du Vector de la fonction main. Ce qui veux dire que tes fonctions ne modifie pas le Vector de la fonction main.

        En C les paramètres de fonction sont uniquement passé par copie !

        La solution est de passer l'adresse de la variable à la fonction, afin que celle-ci puisse la modifier ! (C'est comme cela que fonctionne scanf pour pouvoir modifier les variables).

        Il est inutile de transtyper le retour de la fonction malloc.

        Il faudra revoir aussi la gestion de l'ajout d'éléments quand tu dépasses la capacité de l'allocation. 

        Normalement on mets un champs capacite qui est la taille de l'allocation, et un champs taille qui est le nombre d'élément valide.

        • Partager sur Facebook
        • Partager sur Twitter
          5 avril 2021 à 21:22:15

          Salut,

          En effet, quand on voit ton vector, tes fonctions ajoutCle (push_back) et supprCle (pop_back) réallouent à chaque fois : c'est très violent.

          L'idée de stocker 2 tailles : la capacite, et la taille (reelle), la capacité est toujours plus grande ou égale à la taille.

          La capacité, c'est la taille que tu as alloué, une réserve)

          Quand tu push_back, s'il reste de la place dans la capacité, alors l'ajout est gratuit ! pas de réalloc.

          Sinon, alors il faut réallouer, et en général on realloue du double de la taille.

          Pour la suppression, soit on ne réalloue pas... On laisse la réserve, et la suppression est gratuite aussi.

          Soit on réalloue de la moitié si on descend en dessus du tiers de la capacité.

          (Pour na pas gacher de mémoire, on pourra envisager une méthode shrink_to_fit qui, une fois appelée, réalloue pile de la taille qu'il faut. On appelle shrink_to_fit si on sait qu'on ne fera plus de push.)

          -
          Edité par Fvirtman 5 avril 2021 à 21:23:14

          • Partager sur Facebook
          • Partager sur Twitter

          Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

            5 avril 2021 à 21:24:34

            Super merci pour vos réponses. Si j'ai bien compris, le champ capacité est celui a realloué si jamais il n'y a pas assez de place ? Et dans ta fonction vector_new WhiteCrow, la variable est créée au sein de la fonction, on ne peut pas la renvoyer alors, non ? Par contre je n'avais pas vraiment compris les pointeurs sur Structure, là avec ton code je comprends beaucoup mieux ! J'essaye ça demain et je donnerai des nouvelles ;)

            Edit : Je viens de voir ton Message Fvirtman, mais comment supprimer un élément sans diminuer la taille du tableau ? Et aussi je me demandais si il existait un butoir pour les tableaux en C, comme pour les chaîne de caractères où le butoir est '\0'. Cela permettrait de connaître la taille d'un tableau a tout instant avec une simple boucle while non ?

            -
            Edité par EjeXjdk 5 avril 2021 à 21:29:20

            • Partager sur Facebook
            • Partager sur Twitter
              5 avril 2021 à 21:37:42

              > Sinon, alors il faut réallouer, et en général on réalloue du double de la taille.

              Il faut préciser (parce que c'est contre-intuitif), que la stratégie doublement de la taille fait que le coût d'un ajout, dans le tableau est constant. (techniquement, le coût amorti moyen).

              Parce qu'intuitivement, on se dit, houlala, chaque fois que je dois doubler la taille, ça va me coûter chaud parce qu'il faut tout recopier.

              Oui mais non.

              On double rarement, justement. De moins en moins souvent. Alors que le stratégie "on augmente d'un", ça va nécessiter d'augmenter à chaque fois, et donc recopier/reloger tout ce qu'on a déjà. Bref, il faut une analyse plus précise.

              Exemple, si on est parti de 1 et qu'on arrive à 1000 on a copié 1 élément (en passant à 2), puis 2 (en passant à 4) etc. Soit 1+2+4+8+16+... 512, soit 1023 copies. C'est de l'ordre de grandeur du nombre d'ajouts qui ont été faits.

              Après on peut aussi considérer la taille mémoire nécessaire : la capacité, c'est au max 2 fois le nombre d'éléments présents simultanément.

              -
              Edité par michelbillaud 5 avril 2021 à 21:39:02

              • Partager sur Facebook
              • Partager sur Twitter
                6 avril 2021 à 9:36:32

                EjeXjdk a écrit:

                Edit : Je viens de voir ton Message Fvirtman, mais comment supprimer un élément sans diminuer la taille du tableau ? Et aussi je me demandais si il existait un butoir pour les tableaux en C, comme pour les chaîne de caractères où le butoir est '\0'. Cela permettrait de connaître la taille d'un tableau a tout instant avec une simple boucle while non ?


                Comme je te disais, dans ta structure, tu as juste une taille, une cacapité :

                struct Vector
                {
                   int* data;
                   int taille,capacite;
                }


                Donc supprimer un élément (à la fin) sans bouger la capacité du tableau (donc sans réalloc), c'est juste taille--;

                capacite ne bouge pas. 

                Et sinon, non il n'y a pas de \0 dans le cas d'un tableau : car toute valeur peut être valide, contrairement à une chaine ou le \0 (le 0) a été défini comme caractère spécial. 

                • Partager sur Facebook
                • Partager sur Twitter

                Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                  6 avril 2021 à 9:56:43

                  L'idée d'un "butoir" pour marquer la fin (ce qu'on appelle une valeur sentinelle) est rarement une bonne idée.

                  Raison : pour retrouver la fin ou déterminer le nombre d'éléments, il faut parcourir depuis le début.

                  C'est un choix qui a été fait pour les chaînes au début dans le système Unix, puis en C,  basé sur l'idée que

                  • les chaînes c'est fait pour être affiché caractère par caractère dans l'ordre,
                  • on n'affiche jamais le caractère nul, donc on peut s'en servir comme sentinelle, ça prend qu'un octet,
                  • et comme ça on a juste à transmettre le pointeur sur le début, pas la longueur. Hop, un seul registre.

                  un choix tout à fait contestable. Et vigoureusement déploré ensuite, mais c'était trop tard.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    6 avril 2021 à 10:08:02

                    michelbillaud a écrit:

                    • les chaînes c'est fait pour être affiché caractère par caractère dans l'ordre,


                    C'est encore plus vrai avec la généralisation de l'UTF8 qui clos définitivement la possibilité d'un accès aléatoire, et impose un accès séquentiel :)

                    (c'est à dire qu'on ne peut pas demander quelle est la 12e lettre sans avoir commencé depuis le début)

                    Mais moi je ne le trouve pas si bête ce choix la. En effet, il est rare qu'on ait besoin de la 12e lettre sans s'être tapé les autres.

                    • Partager sur Facebook
                    • Partager sur Twitter

                    Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                      6 avril 2021 à 10:23:55

                      On peut aussi dire que l'UTF-8 est un codage "externe" qu'on s'empressera de convertir, à la lecture, quand on doit faire du traitement de chaînes un tant soi peu calculatoire, en caractères Unicode de taille fixe sur 32 bits.



                      -
                      Edité par michelbillaud 6 avril 2021 à 10:26:00

                      • Partager sur Facebook
                      • Partager sur Twitter
                        6 avril 2021 à 13:39:58

                        EjeXjdk a écrit:

                        Super merci pour vos réponses. Si j'ai bien compris, le champ capacité est celui a realloué si jamais il n'y a pas assez de place ? Et dans ta fonction vector_new WhiteCrow, la variable est créée au sein de la fonction, on ne peut pas la renvoyer alors, non ? Par contre je n'avais pas vraiment compris les pointeurs sur Structure, là avec ton code je comprends beaucoup mieux ! J'essaye ça demain et je donnerai des nouvelles ;)

                        Edit : Je viens de voir ton Message Fvirtman, mais comment supprimer un élément sans diminuer la taille du tableau ? Et aussi je me demandais si il existait un butoir pour les tableaux en C, comme pour les chaîne de caractères où le butoir est '\0'. Cela permettrait de connaître la taille d'un tableau a tout instant avec une simple boucle while non ?

                        -
                        Edité par EjeXjdk il y a environ 15 heures

                        Alors je vois que c'est encore un poil confus …

                        Prenons une image, celle du classeur. Au début de l'année tu vas allouer un classeur pour une matière. Tu vas mettre 50 feuilles dedans pensant que ce sera largement suffisant. C'est la même chose avec ton classeur (=l'objet de type vector) qui contient des feuilles (l'espace pour les données) au nombre de 50 (la capacité) et pour l'instant aucune (taille) n'est gribouillée.

                        Un peu plus tard tu auras toujours le même classeur (le même objet de type vector) toujours avec 50 feuilles mais les 50 sont gribouillées ! Du coup tu vas en rajouter disons 50 en estimant que ça devrait être bon. Du coup tu as plus d'espace pour écrire = tu as incrémenté la capacité de 50 à 100 et réalloué de l'espace pour les données (les feuilles en plus). Tant que tu n'as pas utilisé de nouvelle feuille, la taille reste la même.

                        Bon la métaphore ne sera pas plus utile après …

                        Mais en gros effacer un élément n'affecte pas la capacité, mais uniquement la taille. Soit tu décrémentes la taille (et c'est tout) sachant que l'élément que tu vas écrire (push back) sera data[taille] et que tu fais un taille++ ; soit tu es obligé de procéder par écrasement comme dans le cas d'un tableau normal si tu veux effacer un élément quelconque.

                        Tu peux utiliser une valeur spéciale qui servirait d'indicateur de fin de tableau, à l'instar du \0 pour les c strings. Cela te permettrait de t'affranchir de l'attribut taille, mais pas de la capacité. Tu serais à chaque insertion obligé de parcourir toutes les données pour trouver la fin et faire un push back. En revanche cela ne change pas forcément grand chose en cas d'insertion aléatoire.
                        Le truc qui change est que tu ne pourras plus utiliser la valeur de fin de tableau que comme valeur de fin de tableau … jamais comme valeur utile.
                        Mis à part dans les C strings, on peut retrouver cette technique dans les tableaux de pointeurs comme argv par exemple. On connaît la taille mais le dernier élément doit être un pointeur NULL ⇒ argv[argc] == NULL. On le retrouvera aussi souvent dans les tableaux statiques, un opinteur NULL pouvant indiquer la fin du tableau (pour justement s'affranchir de trimbaler une taille) comme avec les tableaux d'option de lignes de commande à la GNU ou parfois comme indicateur de fin de liste d'arguments avec les fonctions variadiques.

                        • Partager sur Facebook
                        • Partager sur Twitter
                          6 avril 2021 à 22:44:30

                          Bonsoir, merci pour toutes vos réponses qui m'ont aidé à comprendre de nombreuses choses ! J'ai donc corriger totalement mon code pour le remplacer par ceci :

                          Vector.h :

                          #include <stdio.h>
                          #include <stdlib.h>
                          
                          
                          typedef struct Vector Vector;
                          struct Vector{
                            int capacity;
                            int size;
                            int* data;
                          };
                          
                          
                          Vector* creerVector(void){
                            Vector* V = malloc(sizeof(*V));
                            if(V != NULL){
                              V->capacity = 2;
                              V->size = 0;
                              V->data = malloc(sizeof(*V->data) * (V->capacity));
                            
                              if(V->data != NULL){
                                return V;
                              }
                          
                          
                              else{
                                printf("Erreur lors de l'allocation du conteneur");
                                exit(EXIT_FAILURE);
                              }
                            }
                            else{
                              printf("Erreur lors de l'allocation du Vector");
                                exit(EXIT_FAILURE);
                            }
                          }
                              
                          
                          void ajoutEle(Vector* V, int e){
                            if((V->size)>=(V->capacity)){
                              V->data = realloc(V->data, sizeof(*V->data) * (2*V->capacity));
                              V->capacity *= 2;
                              *(V->data + (V->size)) = e;
                              (V->size)++;
                              
                            }
                            else{
                              *(V->data + (V->size)) = e;
                              (V->size)++;
                            }
                          }
                          void supprEle(Vector* V){
                            V->size--;
                          }
                            
                          
                          void printInfo(Vector V){
                            printf("Capacity : %d, ", V.capacity);
                            printf("Size : %d\n", V.size);
                            printf("Les éléments sont : [%d", *(V.data));
                            for(int i = 1; i < (V.size); i++){
                              printf(", %d", *(V.data + i));
                            }
                            printf("]\n");
                          }



                          Vector.c :

                          #include <stdio.h>
                          #include <stdlib.h>
                          #include "Vector.h"
                          
                          
                          int main(void){
                            Vector* V = creerVector();
                            printInfo(*V);
                            ajoutEle(V, 2);
                            printInfo(*V);
                            ajoutEle(V, 3);
                            printInfo(*V);
                            ajoutEle(V, 4);
                            ajoutEle(V, 12);
                            printInfo(*V);
                            supprEle(V);
                            printInfo(*V);
                          
                            return 0;
                          }
                            



                          Ce qui, après compilation et execution me renvoie :

                          Capacity : 2, Size : 0
                          Les éléments sont : [0]
                          Capacity : 2, Size : 1
                          Les éléments sont : [2]
                          Capacity : 2, Size : 2
                          Les éléments sont : [2, 3]
                          Capacity : 4, Size : 4
                          Les éléments sont : [2, 3, 4, 12]
                          Capacity : 4, Size : 3
                          Les éléments sont : [2, 3, 4]
                          



                          Je compte coder encore plusieurs fonctions ce soir et plus tard quand j'aurais le temps. Cependant, il y a encore quelques points que je ne comprends pas, à commencer par cette ligne de WhiteCrow :

                          new->data = malloc( new->capacity * sizeof *new->data );

                          Pourquoi faire sizeof(*new->data) ? Je ne suis pas sûr de bien comprendre l'utilité et le fonctionnement de cette ligne. Aussi, je ne comprends toujours pas pourquoi le compilateur ne râle pas lorsqu'on renvoie "t_vector" alors qu'il a été créée dans la fonction elle-même.

                          Pourquoi le choix de mettre une valeur sentinelle en fin de chaîne de char est contestable ?

                          Et pourquoi si dans les tableaux statiques on peut marquer la fin avec un pointeur à NULL on ne peut pas faire la même chose pour les tableaux dynamiques ? Quel serait l'intérêt de stocker le pointeur NULL dans mon tableau ^^?





                          -
                          Edité par EjeXjdk 6 avril 2021 à 23:14:30

                          • Partager sur Facebook
                          • Partager sur Twitter
                            7 avril 2021 à 1:00:43

                            EjeXjdk a écrit:

                            Bonsoir, merci pour toutes vos réponses qui m'ont aidé à comprendre de nombreuses choses ! J'ai donc corriger totalement mon code pour le remplacer par ceci :

                            Vector.h :

                            #include <stdio.h>
                            #include <stdlib.h>
                            
                            
                            typedef struct Vector Vector;
                            struct Vector{
                              int capacity;
                              int size;
                              int* data;
                            };
                            
                            
                            Vector* creerVector(void){
                              Vector* V = malloc(sizeof(*V));
                              if(V != NULL){
                                V->capacity = 2;
                                V->size = 0;
                                V->data = malloc(sizeof(*V->data) * (V->capacity));
                              
                                if(V->data != NULL){
                                  return V;
                                }
                            
                            
                                else{
                                  printf("Erreur lors de l'allocation du conteneur");
                                  exit(EXIT_FAILURE);
                                }
                              }
                              else{
                                printf("Erreur lors de l'allocation du Vector");
                                  exit(EXIT_FAILURE);
                              }
                            }
                                
                            
                            void ajoutEle(Vector* V, int e){
                              if((V->size)>=(V->capacity)){
                                V->data = realloc(V->data, sizeof(*V->data) * (2*V->capacity));
                                V->capacity *= 2;
                                *(V->data + (V->size)) = e;
                                (V->size)++;
                                
                              }
                              else{
                                *(V->data + (V->size)) = e;
                                (V->size)++;
                              }
                            }
                            void supprEle(Vector* V){
                              V->size--;
                            }
                              
                            
                            void printInfo(Vector V){
                              printf("Capacity : %d, ", V.capacity);
                              printf("Size : %d\n", V.size);
                              printf("Les éléments sont : [%d", *(V.data));
                              for(int i = 1; i < (V.size); i++){
                                printf(", %d", *(V.data + i));
                              }
                              printf("]\n");
                            }



                            Vector.c :

                            #include <stdio.h>
                            #include <stdlib.h>
                            #include "Vector.h"
                            
                            
                            int main(void){
                              Vector* V = creerVector();
                              printInfo(*V);
                              ajoutEle(V, 2);
                              printInfo(*V);
                              ajoutEle(V, 3);
                              printInfo(*V);
                              ajoutEle(V, 4);
                              ajoutEle(V, 12);
                              printInfo(*V);
                              supprEle(V);
                              printInfo(*V);
                            
                              return 0;
                            }
                              



                            Ce qui, après compilation et execution me renvoie :

                            Capacity : 2, Size : 0
                            Les éléments sont : [0]
                            Capacity : 2, Size : 1
                            Les éléments sont : [2]
                            Capacity : 2, Size : 2
                            Les éléments sont : [2, 3]
                            Capacity : 4, Size : 4
                            Les éléments sont : [2, 3, 4, 12]
                            Capacity : 4, Size : 3
                            Les éléments sont : [2, 3, 4]

                             > Si tu cherches de l'inspiration, regarde la liste des fonctions membres sur C++ Reference et, si possible, reprend les mêmes noms (push_back, pop_back etc...), les utilisateurs de ton API te seront reconnaissants de ne pas chambouler leurs habitudes;).

                            Je compte coder encore plusieurs fonctions ce soir et plus tard quand j'aurais le temps. Cependant, il y a encore quelques points que je ne comprends pas, à commencer par cette ligne de WhiteCrow :

                            new->data = malloc( new->capacity * sizeof *new->data );

                            Pourquoi faire sizeof(*new->data) ? Je ne suis pas sûr de bien comprendre l'utilité et le fonctionnement de cette ligne. Aussi, je ne comprends toujours pas pourquoi le compilateur ne râle pas lorsqu'on renvoie "t_vector" alors qu'il a été créée dans la fonction elle-même.

                            > Tu peux le remplacer par size of int, mais si au lieu de faire un vector de int, tu veux faire un vector de double, il te faudra adapter toutes tes fonctions à ce nouveau type de donnée, en général on préfère éviter ce genre de choses. Une autre solution consisterait à créer un vector de void* pour accomoder tous les types. Bon courage si tu t'engages sur cette voie;).

                            > t_vector est un alias de Vector.

                            Pourquoi le choix de mettre une valeur sentinelle en fin de chaîne de char est contestable ?

                            Et pourquoi si dans les tableaux statiques on peut marquer la fin avec un pointeur à NULL on ne peut pas faire la même chose pour les tableaux dynamiques ? Quel serait l'intérêt de stocker le pointeur NULL dans mon tableau ^^?

                            > Parce qu'il te faudra décaler ce NULL (et toutes les données qui le précède) à chaque fois que tu enregistre une donnée dans ton vector. Le template vector en C++ garantit que l'insertion à la fin du tableau sera constante. Si tu veux faire faire/refaire une API, il faut aussi que tu t'en tiennes à ses garanties;).

                            -
                            Edité par VaultBoy 7 avril 2021 à 1:06:03

                            • Partager sur Facebook
                            • Partager sur Twitter
                              7 avril 2021 à 3:43:17

                              Une chose qui ne semble pas avoir   été   dite est qu'on ne met pas le code des fonctions dans les fichiers .h
                              P.S. On est censé être dans la catégorie C et non C++
                              • Partager sur Facebook
                              • Partager sur Twitter

                              Le Tout est souvent plus grand que la somme de ses parties.

                                7 avril 2021 à 6:41:19

                                Reprenons ton code, commencons par 

                                Vector* creerVector(void){
                                  Vector* V = malloc(sizeof(*V));
                                  if(V != NULL){
                                    V->capacity = 2;
                                    V->size = 0;
                                    V->data = malloc(sizeof(*V->data) * (V->capacity));
                                   
                                    if(V->data != NULL){
                                      return V;
                                    }
                                 
                                 
                                    else{
                                      printf("Erreur lors de l'allocation du conteneur");
                                      exit(EXIT_FAILURE);
                                    }
                                  }
                                  else{
                                    printf("Erreur lors de l'allocation du Vector");
                                      exit(EXIT_FAILURE);
                                  }
                                }

                                Alors d'où sort ce 2, pourquoi 2 ?
                                OK … c'est la capacité initiale et tu as arbitrairement choisi 2. En fait on ne balance pas une constante comme ça dans le code car on oublie vite ce que c'est et pourquoi on a choisi cette valeur. De plus si elle apparaît à plusieurs endroit de ton code il faudra modifier les bonnes occurrences de 2 si tu décides de changer de capacité initiale, ou si tu décides de la rendre configurable …

                                Il vaut mieux (dans le cas d'une capacité initiale non configurable au runtime) utiliser un #define pour les constantes comme par exemple :

                                #definei INITIAL_CAPACITY ((size_t) (2))
                                
                                Vector* creerVector(void){
                                  Vector* V = malloc(sizeof(*V));
                                  if(V != NULL){
                                    V->capacity = INITIAL_CAPACITY;
                                    V->size = 0;
                                    V->data = malloc(sizeof(*V->data) * (V->capacity));
                                   
                                    if(V->data != NULL){
                                      return V;
                                    }
                                 
                                 
                                    else{
                                      printf("Erreur lors de l'allocation du conteneur");
                                      exit(EXIT_FAILURE);
                                    }
                                  }
                                  else{
                                    printf("Erreur lors de l'allocation du Vector");
                                      exit(EXIT_FAILURE);
                                  }
                                }

                                Je profite ici pour expliquer la ligne

                                V->data = malloc(sizeof(*V->data) * (V->capacity));

                                Elle se lit en français comme : le champs data contiendra l'adresse d'une zone mémoire qui aura été allouée pour contenir au moins V->capacity éléments, chacun ayant une taille de ce qu'on a décidé sur quoi pointerait V->data. En l'occurrence des int, mais si tu refactores en double ce sera des doubles et tu n'auras pas besoin de changer ton code.

                                Ensuite passons à

                                void ajoutEle(Vector* V, int e){
                                  if((V->size)>=(V->capacity)){
                                    V->data = realloc(V->data, sizeof(*V->data) * (2*V->capacity));
                                    V->capacity *= 2;
                                    *(V->data + (V->size)) = e;
                                    (V->size)++;
                                     
                                  }
                                  else{
                                    *(V->data + (V->size)) = e;
                                    (V->size)++;
                                  }
                                }

                                Déjà, le nom de la fonction est pourri.  Alors ok, je comprends que la fonction ajoute un élément. Mais à quoi ? Un tableau ? une liste ? une table de hash ?

                                Quand tu développes une API en C, il est toujours de bon ton de préfixer toutes les primitives avec le même préfixe → au premier coup d'œil tu sais à quelle partie de l'API ça correspond, où chercher la doc, quels sont les paramètres attendus (car ils suivent aussi une logique) …
                                Avec la fonction qui crée un vecteur tu postfixes … c'est ok aussi. Mais du coup tu aurais dû appeler cette fonction ajouterElementVector.

                                Sinon :

                                • (V->size)++ peut s'écrire V->size++ ⇒ ça c'est pas très grave … ;
                                • *(V->data + (V->size)) doit plutôt s'écrire V->data[ V->size ] ; même si c'est la même chose la seconde écriture est plus commune et ne déclenche pas dans la tête du lecteur une traduction … donc un effort;
                                • ces deux lignes sont dans le IF et le ELSE … tu peux factoriser et rendre ton code un peu plus lisible ⇒
                                  au lieu de structurer en 
                                  SI je dois agrandir ALORS
                                      blabla bla
                                  SINON
                                      blablabla
                                  FSIN

                                  car dans tous les cas tu ajoutes les éléments …
                                  SI je dois agrandir ALORS j'agrandi FSI
                                  j'ajoute
                                • ce qui m'amène à te faire remarquer qu'écrire une fonction qui s'occupe uniquement de l'agrandissement serait une bonne chose …
                                  Et à nouveau sans constante qui tombe de nulle part …

                                  #define DEFAULT_GROW_FACTOR ((size_t) 2)
                                  
                                  bool vector_check_size(t_vector *V)
                                  {
                                      if (V->size >= V->capacity) {
                                          int *tmp=realloc( V->data, DEFAULT_GROW_FACTOR*V->capacity );
                                          if (tmp) {
                                              V->data=tmp;
                                              V->capacity *= DEFAULT_GROW_FACTOR;
                                              return true;
                                           } else {
                                               return false;
                                      }
                                      return true;
                                  }
                                  Ici tu as une fonction qui essaye de réallouer l'espace mémoire correctement. Comme la réallocation peut échouer il faut vérifier … et lire la man page de realloc. J'ai pris le parti de lui faire renvoyer un booléen indiquant si la réallocation a ou non échouée. En effet si c'est le cas, les anciennes données ne sont pas perdues et on peut toujours essayer de faire quelque chose avec avant de planter …

                                  void vector_push_back(t_vector *V, int e)
                                  {
                                      if (vector_check_size(V))
                                          V->data[V->size++]=e; // écriture très compacte équivalente à ton code
                                      else
                                          < on gère l'erreur >
                                  }



                                Passons à la suite :

                                void supprEle(Vector* V){
                                  V->size--;
                                }

                                mmm il se passe quoi si tu essaye de supprimer un élément d'un vecteur vide ?
                                Ça mnanque d'ailleurs une petite fonction qui me dit si le vecteur est ou non vide …

                                Comme dans :

                                 
                                void printInfo(Vector V){
                                  printf("Capacity : %d, ", V.capacity);
                                  printf("Size : %d\n", V.size);
                                  printf("Les éléments sont : [%d", *(V.data));
                                  for(int i = 1; i < (V.size); i++){
                                    printf(", %d", *(V.data + i));
                                  }
                                  printf("]\n");
                                }

                                Je comprends le pourquoi du comment … c'est histoire de faire beau …

                                EjeXjdk a écrit:

                                Pourquoi le choix de mettre une valeur sentinelle en fin de chaîne de char est contestable ?

                                C'est plus une question de religion au final …

                                N'avoir qu'une sentinelle de fin t'oblige à parcourir tout le contenu pour en connaître la taille … ce qui n'est a priori pas très efficace. Du moins beaucoup moins que de conserver la taille dans un champ. En revanche si tu conserves la taille alors il te faudra la mettre à jour à chaque modification l'impactant.

                                EjeXjdk a écrit:

                                Et pourquoi si dans les tableaux statiques on peut marquer la fin avec un pointeur à NULL on ne peut pas faire la même chose pour les tableaux dynamiques ? Quel serait l'intérêt de stocker le pointeur NULL dans mon tableau ^^?

                                -

                                Edité par EjeXjdk il y a environ 6 heures

                                Bah comme tu as un tableau d'int, y stocker un pointeur sera pour le moins problématique … non ?

                                Si tu avais un tableau de pointeurs sur int ce serait autre chose.

                                Et je t'encourage non seulement à le faire mais à ensuite comparer les performances entre les deux solutions dans différents cas de figures …

                                Je t'encourage aussi vivement à écrire des petites fonctions de test … et éventuellement commencer à essayer de chercher sur internet les assertions … le fameux assert.h ; si tu as le temps.

                                Edit: petits typos + refactorisation
                                deux trois trucs à revoir comme la reallocation qui n'est hyper correcte, mais suffisante dans ce contexte.

                                -
                                Edité par White Crow 7 avril 2021 à 6:49:47

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  7 avril 2021 à 11:04:40

                                  White Crow a écrit:

                                  EjeXjdk a écrit:

                                  Pourquoi le choix de mettre une valeur sentinelle en fin de chaîne de char est contestable ?

                                  C'est plus une question de religion au final …

                                  N'avoir qu'une sentinelle de fin t'oblige à parcourir tout le contenu pour en connaître la taille … ce qui n'est a priori pas très efficace. Du moins beaucoup moins que de conserver la taille dans un champ. En revanche si tu conserves la taille alors il te faudra la mettre à jour à chaque modification l'impactant.

                                  Edité par White Crow il y a environ 3 heures


                                  C'est pas une question de religion. Il y a des critères rationnels.

                                  • si on n'a pas besoin de la taille : on s'en fout
                                  • si on en a besoin :
                                  1. avec la sentinelle : chaque fois qu'on en a besoin, il faut la recalculer, ce qui prend un temps proportionnel à la longueur de la chaine
                                  2. avec un compteur : quand on en a besoin on y a accès directement. Et quand on modifie la chaine, la modification de la taille se fait aussi directement. Et si l'accès aux chaînes est "encapsulé" correctement dans des fonctions (le minimum qu'on peut attendre de la bibliothèque d'un langage qui manipule des chaines...) il n'y a pas à y penser ou à risquer d'oublier.
                                  Donc y a pas photo.
                                  Le problème, c'est que C est un langage qui, au départ, était officiellement un langage sans chaînes de caractères (intégrées au langage), comme en témoigne l'introduction du K & R :

                                  Il se trouve que les programmeurs se sont débrouillés avec l'ersatz qui constitue le pointeur sur le premier caractère + valeur sentinelle, sans indication de la taille ni de la capacité maximale. Et que les fonctions qui jouaient avec se sont retrouvées dans la norme, donc consacrées comme "la" façon de faire en C. Un petit souci de conception, qui, finalement, aura causé des milliards de dollars de dégâts en termes de plantages et piratages par buffer overflow.
                                  Bref, l'aspect religieux là dedans, c'est peut être le refus de rentrer dans des choix rationnels.


                                  PS : à la même époque, en Pascal, il n'y avait pas non de chaines de caractères dans le standard. Juste des tableaux de caractères de taille fixe, complétés par des espaces à droite au besoin.  Alors que Basic, si.

                                  -
                                  Edité par michelbillaud 7 avril 2021 à 11:17:54

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    7 avril 2021 à 17:32:03

                                    Dans la plupart des cas, Basic était un langage interprété, mais j'ai connu un cas où il était compilé.
                                    Je ne me rappelle pas ce qu'on faisait pour allonger une chaîne.
                                    • Partager sur Facebook
                                    • Partager sur Twitter

                                    Le Tout est souvent plus grand que la somme de ses parties.

                                      7 avril 2021 à 18:42:36

                                      Avec +. Le guide GW BAsic, chapitre 6

                                      http://www.antonis.de/qbebooks/gwbasman/chapter%206.html

                                      Strings can be concatenated by using the plus (+) sign. For example:

                                      10 A$="FILE":B$="NAME"
                                      20 PRINT A$+B$
                                      30 PRINT "NEW " + A$+B$
                                      RUN
                                       FILENAME
                                       NEW FILENAME

                                       donc pour ajouter quelque chose au bout : concaténer et réaffecter

                                      A$ = A$ + "*"



                                      -
                                      Edité par michelbillaud 7 avril 2021 à 18:49:50

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        7 avril 2021 à 19:03:00

                                        Michel, Pierrot, je vous propose de nous diriger vers un autre sujet → NUL terminated strings en C afin de laisser à EjeXjdk un sujet propre et pas trop embrouillé par du HS.
                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          8 avril 2021 à 22:06:24

                                          Bonsoir et encore merci pour toutes vos réponses ! Je pense avoir compris les points que j'avais mal assimilés, et je vais donc rajouter des fonctions dans mon code. Aussi, comme la remarquer Pierrot, j'ai écris le code de mes fonctions dans le fichier .h, mais pourquoi il ne faut pas les écrire dedans ? Et a quoi sert un fichier .h si on ne met pas la définition des fonctions à l'intérieur ?

                                          Ensuite, c'est vrai que quand j'ai choisi la capacité initiale a 2 je savais que ce que je faisais n'était pas très reglementaire :)

                                          Par contre, je n'ai toujours pas compris pourquoi, dans le premier message de WhiteCrow, la fonction "vector_new" peut renvoyer une variable locale à cette fonction ?

                                          t_vector *vector_new(void)
                                          {
                                              t_vector *new=malloc( sizeof *new );
                                              if (new) {
                                                  new->capacity = INITIAL_CAPACITY;
                                                  new->size = 0;
                                                  new->data = malloc( new->capacity * sizeof *new->data );
                                                  if (new->data) {
                                                      // cool tout c'est bien passé !
                                                      return new;
                                                  } else {
                                                      // pb lors de l'allocation du conteneur
                                                      // faut gérer
                                                      free(new);
                                                      < là on gère suivant les conventions >
                                                  }
                                              } else {
                                                  // pb d'allocation dès le départ
                                                  // faut gérer aussi selon les conventions
                                                  ....
                                              }
                                          }



                                          -
                                          Edité par EjeXjdk 8 avril 2021 à 22:06:37

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            8 avril 2021 à 22:25:56

                                            EjeXjdk a écrit:

                                            Bonsoir et encore merci pour toutes vos réponses ! Je pense avoir compris les points que j'avais mal assimilés, et je vais donc rajouter des fonctions dans mon code. Aussi, comme la remarquer Pierrot, j'ai écris le code de mes fonctions dans le fichier .h, mais pourquoi il ne faut pas les écrire dedans ? Et a quoi sert un fichier .h si on ne met pas la définition des fonctions à l'intérieur ?

                                            En très gros le header (le .h) sert à partager à plusieurs modules (fichiers) les déclarations (les prototypes de fonctions) qui seront ensuite définies (implémentées/codées) dans un seul et unique source (le .c qui va avec). Mettre du code dans le header c'est dupliquer le code partout où tu inclues le header ⇒ généralement pas bon.

                                            EjeXjdk a écrit:

                                            Par contre, je n'ai toujours pas compris pourquoi, dans le premier message de WhiteCrow, la fonction "vector_new" peut renvoyer une variable locale à cette fonction ?

                                            t_vector *vector_new(void)
                                            {
                                                t_vector *new=malloc( sizeof *new );
                                                if (new) {
                                                    new->capacity = INITIAL_CAPACITY;
                                                    new->size = 0;
                                                    new->data = malloc( new->capacity * sizeof *new->data );
                                                    if (new->data) {
                                                        // cool tout c'est bien passé !
                                                        return new;
                                                    } else {
                                                        // pb lors de l'allocation du conteneur
                                                        // faut gérer
                                                        free(new);
                                                        < là on gère suivant les conventions >
                                                    }
                                                } else {
                                                    // pb d'allocation dès le départ
                                                    // faut gérer aussi selon les conventions
                                                    ....
                                                }
                                            }



                                            -
                                            Edité par EjeXjdk il y a 11 minutes

                                            Elle ne renvoie pas une variable locale, elle renvoie la valeur de l'adresse donnée par le malloc …  tu renvoies un pointeur qui pointe sur une adresse valide. Tu peux copier la valeur du pointeur (l'adresse pointée) cela ne changera pas l'endroit pointé (même adresse toujours valide car allouée par malloc).

                                            Ce qui est en revanche interdit est de renvoyer l'adresse d'une variable locale automatique (=non statique) comme par exemple :

                                            int  *PAS_BON(void)
                                            {
                                                int XLII = 42;
                                                return &XLII;
                                            }

                                            Ici la variable locale XLII n'existe plus à la fin de la fonction (sa durée de vie est limitée à l'appel de la fonction), renvoyer une adresse qui pointe vers elle est renvoyer une adresse qui pointe à un endroit qui n'est plus valide ⇒ faut pas s'attendre à quoi que ce soit de bon !

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              8 avril 2021 à 22:58:09

                                              Super merci ! Et une dernière chose, tu m'avais dit que c'était mieux de généraliser le code pour avoir le moins de changement à faire si on souhaite modifier certaine parties. Dans cette fonction :

                                              #define DEFAULT_GROW_FACTOR ((size_t) 2)
                                               
                                              bool vector_check_size(t_vector *V)
                                              {
                                                  if (V->size >= V->capacity) {
                                                      int *tmp=realloc( V->data, DEFAULT_GROW_FACTOR*V->capacity );
                                                      if (tmp) {
                                                          V->data=tmp;
                                                          V->capacity *= DEFAULT_GROW_FACTOR;
                                                          return true;
                                                       } else {
                                                           return false;
                                                  }
                                                  return true;
                                              }

                                              Y'a-t-il un moyen de remplacer le

                                              nt *tmp=realloc( V->data, DEFAULT_GROW_FACTOR*V->capacity );

                                              par quelque chose qui pourrait prendre en charge des float, voir même une autre structure de données ?

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                8 avril 2021 à 23:29:13

                                                EjeXjdk a écrit:

                                                [...]

                                                Y'a-t-il un moyen de remplacer le

                                                nt *tmp=realloc( V->data, DEFAULT_GROW_FACTOR*V->capacity );

                                                par quelque chose qui pourrait prendre en charge des float, voir même une autre structure de données ?

                                                Arg … la généricité. Dur dur en C.

                                                Alors il y a plusieurs moyens : Au lieu de créer des structures de donnée pour chaque type particulier, 

                                                • tu n'en crées qu'une sur des void *.
                                                  void * est un pointeur générique, il peut pointer sur tout type de données (dans la majorité des cas pour une majorité de plateformes, en gros tout le temps pour toi).
                                                  Au lieu d'insérer des objets, tu inséreras des pointeurs sur ces objets. Là tu n'auras qu'un problème de transtypage et de propriété. C'est au propriétaire des données pointées de libérer les ressources → c'est à toi de décider s'il y a ou non un transfert de propriété lors d'une insertion (par exemple) ;


                                                • tu n'en crées qu'une qui copieras toutes les données qu'on lui donne. Il te faut la taille des objets que tu manipuleras, et tu réserveras l'espace nécessaire pour les stocker. Tu en seras propriétaire, etc … ;


                                                • tu deviens un pro de la macro et tu écris un squelette de programme en laissant un type «libre». Tu crées les implémentations ou à la demande ou explicitement en définissant ton type. C'est complexe et compliqué, tu peux vite t'embourber avec ça.

                                                … ouaips C a 50 ans … c'est plus facile avec des langages qui ont été implémenté plus récemment.

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  9 avril 2021 à 7:56:07

                                                  Autre solution : dans la structure qui représente le vecteur "generique" avoir un champ qui mémorise la taille des éléments.

                                                  Ça va pas être pratique de toutes façons. C'est pour ça que C++ a été inventé (1983) avant même la normalisation de C (1989).

                                                  -
                                                  Edité par michelbillaud 9 avril 2021 à 7:58:57

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    9 avril 2021 à 20:47:53

                                                    Donc si je comprends bien un pointeur sur un void peut contenir n'importe quel type ou structure de données ? Et est ce que tu aurais un lien pour faire ça avec la macro, car ça m'intéresse aussi ;)

                                                    Effectivement en C++ c'est bien plus facile !

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      9 avril 2021 à 21:10:46

                                                      EjeXjdk a écrit:

                                                      Donc si je comprends bien un pointeur sur un void peut contenir n'importe quel type ou structure de données ? 

                                                      Un pointeur ne contient rien d'autre qu'une adresse (en gros), il ne contient pas de structure de données ou de type, c'est juste un nombre …

                                                      C'est le cas de tous les pointeurs, peu importe leur type (en gros). C'est le type du pointeur qui indique comment on interprète ce qui est pointé quand on le déréférence. Tu peux forcer pas mal de choses en transtypant :

                                                      #include <stdio.h>
                                                      
                                                      int main(void)
                                                      {
                                                      	int num=0xDEADBEEF;
                                                      
                                                      	int *p_int=&num;
                                                      	short *p_short=&num;
                                                      	char *p_char=&num;
                                                      	double *p_double=&num;
                                                      
                                                      	printf("      int → %X\n", num);
                                                      	printf("   *p_int → %X\n", *p_int);
                                                      	printf(" *p_short → %hX\n", *p_short);
                                                      	printf("  *p_char → %hhX\n", *p_char);
                                                      	printf("*p_double → %lf\n", *p_double);
                                                      
                                                      	return 0;
                                                      }
                                                      

                                                      Tu compiles, tu as des warnings, tu vois ce que ça donne à l'exécution.

                                                       make cast
                                                      cc     cast.c   -o cast
                                                      cast.c: In function ‘main’:
                                                      cast.c:8:17: warning: initialization of ‘short int *’ from incompatible pointer type ‘int *’ [-Wincompatible-pointer-types]
                                                          8 |  short *p_short=&num;
                                                            |                 ^
                                                      cast.c:9:15: warning: initialization of ‘char *’ from incompatible pointer type ‘int *’ [-Wincompatible-pointer-types]
                                                          9 |  char *p_char=&num;
                                                            |               ^
                                                      cast.c:10:19: warning: initialization of ‘double *’ from incompatible pointer type ‘int *’ [-Wincompatible-pointer-types]
                                                         10 |  double *p_double=&num;
                                                            |                   ^
                                                      
                                                      $ ./cast 
                                                            int → DEADBEEF
                                                         *p_int → DEADBEEF
                                                       *p_short → BEEF
                                                        *p_char → EF
                                                      *p_double → 33659389935455188780304984447684303215114980122895460002328826120786402486362631451089232283632402044816948459190983329753552530191802975528117147907261890717286400.000000
                                                      $ ./cast 
                                                            int → DEADBEEF
                                                         *p_int → DEADBEEF
                                                       *p_short → BEEF
                                                        *p_char → EF
                                                      *p_double → 0.011807
                                                      

                                                      Tu comprends pourquoi tu as les résultats que tu vois en int ? pourquoi il n'ya pas forcément les mêmes résultats en double ?

                                                      EjeXjdk a écrit:

                                                      Et est ce que tu aurais un lien pour faire ça avec la macro, car ça m'intéresse aussi ;)

                                                      Effectivement en C++ c'est bien plus facile !

                                                      Bah il y a plusieurs liens sur le net … comme par exemple sur SO → https://stackoverflow.com/questions/9401975/type-safe-generic-containers-with-macros 

                                                      En C++ il y a surtout la notion de template. Une notion qui manque en C, mais qui sait … ptêt qu'avec C23 …





                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        9 avril 2021 à 21:26:04

                                                        White Crow a écrit:

                                                        Tu comprends pourquoi tu as les résultats que tu vois en int ? pourquoi il n'ya pas forcément les mêmes résultats en double ?

                                                        Alors je ne suis pas sûr de comprendre exactement pourquoi. Déjà, je ne vois pas pourquoi

                                                        printf("      int → %X\n", num);
                                                        printf("   *p_int → %X\n", *p_int);

                                                        affiche

                                                        int → DEADBEEF
                                                        *p_int → DEADBEEF

                                                        et non

                                                        int → 0xDEADBEEF
                                                        *p_int → 0xDEADBEEF

                                                        En faite, pourquoi toutes les adresses commencent par 0x ?

                                                        Ensuite, je pense que pour le short int et le char ils n'affichent qu'une partie du message car ils sont encodés sur moins d'octets, mais je ne serai pas dire pourquoi il y a seulement la fin du message qui s'affiche.
                                                        Je ne vois pas du tout pourquoi le

                                                        double *p_double=&num;

                                                        a ce comportement, j'aurai dit qu'une conversion de type serait faite mais visiblement non

                                                        PS : je trouve très intéressant ce petit exercice :)

                                                        -
                                                        Edité par EjeXjdk 9 avril 2021 à 21:27:43

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          9 avril 2021 à 22:12:35

                                                          EjeXjdk a écrit:

                                                          White Crow a écrit:

                                                          Tu comprends pourquoi tu as les résultats que tu vois en int ? pourquoi il n'ya pas forcément les mêmes résultats en double ?

                                                          Alors je ne suis pas sûr de comprendre exactement pourquoi. Déjà, je ne vois pas pourquoi

                                                          printf("      int → %X\n", num);
                                                          printf("   *p_int → %X\n", *p_int);

                                                          affiche

                                                          int → DEADBEEF
                                                          *p_int → DEADBEEF

                                                          et non

                                                          int → 0xDEADBEEF
                                                          *p_int → 0xDEADBEEF

                                                          En faite, pourquoi toutes les adresses commencent par 0x ?

                                                          Ce ne sont pas les adresses qui commencent par 0x, mais c'est la notation pour donner un nombre écrit en hexadécimal. Il y a le préfixe 0x pour indiquer une notation hexadécimale, 0 pour l'octal. Ce dernier point t'explique pourquoi un printf("%d\n", 012) affiche 10 …

                                                          EjeXjdk a écrit:

                                                          Ensuite, je pense que pour le short int et le char ils n'affichent qu'une partie du message car ils sont encodés sur moins d'octets, mais je ne serai pas dire pourquoi il y a seulement la fin du message qui s'affiche.
                                                          Je ne vois pas du tout pourquoi le

                                                          double *p_double=&num;

                                                          Alors c'est pas super compliqué. Je suis sur une plateforme x86 donc little endian, ce qui fait qu'en mémoire on a les 8 octets suivants (?? signifiant non initialisé = peut avoir n'importe quelle valeur)

                                                          EF BE AD DE ?? ?? ?? ??

                                                          tous les pointeurs pointent sur le premier octet, sans exception. Maintenant on interprète différemment ce qui est pointé en fonction du type du pointeur.

                                                          Par exemple en déréférençant p_int qui est un pointeur sur int on dit : à partir de cette adresse lit un int (qui ici fait 4 octets), on trouve donc un int valant 0xDEADBEEF. En revanche lorsqu'on utilise p_short on trouve 0xBEEF car un short tient sur 2 octets … d'où le EF pour le p_char. Le double ne donne jamais la même valeur car un double tient sur 8 octets, donc quand tu déréférences un double à partir de cette adresse, le double que tu obtiendras dépendras de la valeur des 4 octets notés ?? qui sont non initialisé et dont la valeur change d'une exécution à l'autre.

                                                          EjeXjdk a écrit:

                                                          a ce comportement, j'aurai dit qu'une conversion de type serait faite mais visiblement non


                                                          PS : je trouve très intéressant ce petit exercice :)

                                                          -
                                                          Edité par EjeXjdk il y a 5 minutes


                                                          Il y a bien une conversion de type … le compilateur te le dit en plus avec les warnings … par exemple dans le cas du :

                                                          cast.c:8:17: warning: initialization of ‘short int *’ from incompatible pointer type ‘int *’ [-Wincompatible-pointer-types]
                                                              8 |  short *p_short=&num;
                                                                |                 ^
                                                          

                                                          il t'avertit : «Attention ! j'ai un pointeur sur un short int mais tu me donnes un pointeur sur int !»
                                                          Ce n'est pas forcément une erreur mais il faut y regarder de plus près car si c'est ce que tu voulais (pour par exemple déterminer l'endianess de la plateforme en mode «je sais ce que je fais») c'est ok, en revanche si tu ne t'y attendais pas alors c'est que tu as loupé quelque chose …

                                                          maintenant void * est un type que le compilo considère comme générique (=fourre tout). On peut lui donner n'importe quelle adresse de n'importe quoi, c'est bon ; on peut aussi dans l'autre sens le fournir à un pointeur typé sans transtypage et ça passe crême. C'est pas pour rien que malloc, par exemple, retourne des pointeur de ce type.

                                                          Par contre, on ne peut pas le déréférencer … 

                                                          Il y a d'autres utilités à ne pas pouvoir déréférencer un pointeur, cela permet de cacher les implémentation soit en se servant d'un void * comme handle soit en passant par l'idiome PImpl ; on appelle ça des pointeurs opaques.

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            9 avril 2021 à 22:36:32

                                                            On a 8 octets car sur ta machine les entiers sont codés sur 8 octets ? Et pourquoi ils sont rangés dans cet ordre :

                                                            EF BE AD DE ?? ?? ?? ??

                                                            et pas dans cet ordre :

                                                            ?? ?? ?? ?? DE AD BE EF

                                                            Sinon, je pense avoir bien compris, encore mille fois merci !

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              9 avril 2021 à 22:44:20

                                                              EjeXjdk a écrit:

                                                              On a 8 octets car sur ta machine les entiers sont codés sur 8 octets ? Et pourquoi ils sont rangés dans cet ordre :

                                                              EF BE AD DE ?? ?? ?? ??

                                                              Non, je montre 8 octets consécutifs. Sur ma plateforme (machine+os+type exécutable) «mes» int font 4 octets, mes short 2, mes char 1 et mes doubles 8.

                                                              Les conventions ne sont pas les mêmes sur toutes les plateformes → https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models 

                                                              EjeXjdk a écrit:

                                                              EF BE AD DE ?? ?? ?? ??

                                                              et pas dans cet ordre :

                                                              ?? ?? ?? ?? DE AD BE EF

                                                              le nombre 0XDEADBEEF est stocké comme une suite de 4 octets valant EF, BE, AD et DE car ma plateforme est little endian. Si elle avait été big endian ce même nombre aurait été stocké comme DE, AD, BE, EF.

                                                              ⇒ https://fr.wikipedia.org/wiki/Boutisme 





                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              Vector en C

                                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                              • Editeur
                                                              • Markdown