Partage
  • Partager sur Facebook
  • Partager sur Twitter

Quelques petites questions

Sujet résolu
    7 juillet 2023 à 23:52:51

    Bonjour, j'ai quelques petites questions et besoin de quelques précision svp

    char *s = "test";
    printf("%s | %p\n", s, s);
    s = "abc";
    printf("%s | %p\n", s, s);

    Que se passe t-il exactement quand je fais ça ? Je veux dire par la que pourquoi il n'a pas la même adresse après lui avoir assigné une nouvelle chaine ?

    Même si c'est ça encore je peux encore le comprendre parce-que du coup il pointe sur une autre chaine, mais dans ce quand se passe t-il quand je fais

    "quelque chose"

    Est t-elle stocké dans la stack ? car les adresses sont très différentes de celle que j'ai l'habitude de print, sur mon pc elle commence plutôt par 0x7ff.... tandis qu'elle commence plutôt par 0x55 si je prend la peine de la mettre dans un [] plutôt que * 

    Et aussi, j'ai remarqué qu'elle existe toujours le temps du programme même après lui avoir assigné la nouvelle chaine, l'ancienne chaine continue d'exister.

    Seconde question, pourquoi je peux pas faire ça ?

    int	main()
    {
    	char dst[100], src[] = "quelque chose";
    
    	while ((*dst++ = *src++))
    		*dst++;
    	*dst = 0;
    	return 0;
    }
    error: lvalue required as increment operand

    tandis qu'en la passant à une fonction c'est ok

    char	*stringcopy(char dst[], char src[])
    {
    	while ((*dst++ = *src++))
    		;
    	*dst = 0;
    	return dst;
    }
    
    int	main()
    {
    	char dst[100], src[] = "quelque chose";
    	stringcopy(dst, src);
    	printf("%s | %s\n", dst, src);
    	return 0;
    }

    Et pourquoi en avoir fait un char * en tant que retour alors que le void fait très bien l'affaire ?

    # include <stdio.h>
    
    void	stringcopy(char dst[], char src[])
    {
    	while ((*dst++ = *src++))
    		;
    	*dst = 0;
    }
    
    int	main()
    {
    	char dst[100], src[] = "quelque chose";
    	stringcopy(dst, src);
    	printf("%s | %s\n", dst, src);
    	return 0;
    }

    3e question, mais malheureusement j'arrive pas à reproduire ce que je voulais vous demander, mais en gros:

    parfois je peux envoyer ma string dans une fonction et la modifier sans soucis mais parfois je suis obligé de la passer par pointeur, pourquoi ? Enfin, si vous voyez ce que je veux dire

    • Partager sur Facebook
    • Partager sur Twitter
      8 juillet 2023 à 0:30:48

      1) Les chaînes littérales sont stockée dans le Data Segment et sont généralement à lecture seule (tu ne peux pas les modifier). 

      Dans ton exemple ce ne sont pas des chaînes que tu assignes, mais un pointeur auquel tu assignes l'adresse des chaînes. (Elles ont chacune leur adresse). Oui elle existe toute la durée du programme.

      2) Parce que dst et src ne sont pas des pointeurs mais des tableaux ! Quand tu passes un tableau à une fonction en fait tu passes son adresse et l'argument est un pointeur. En C on ne peut pas passer directement un tableau à une fonction (sauf s'il est membre d'une structure mais ça c'est autre chose).

      Pour ta fonction stringcopy, c'est la tienne tu lui fais retourner ce que tu veux ! Pour la fonction strcpy de la lib C on lui fait retourner l'adresse de destination, ça peut permettre de la tester directement dans un if ou de l'afficher directement dans un printf par exemple.

      3) tu la passes toujours par pointeur, comme je t'ai dit on ne peux pas passer les tableaux au fonctions. Que tu ne puisse pas la modifier, c'est probablement que ce sont des chaînes à lecture seule comme je t'ai dit plus haut. 

          char tableau_de_char[] = "quelque chose, modifiable";
      
          char *pointeur_sur_char = "non modifiable";

      Bien faire la distinction entre le tableau de char, qui lui est modifiable et le pointeur_sur_char qui pointe sur une chaîne littérale généralement non modifiable. Un pointeur peut aussi pointer sur une chaîne modifiable.

      Pointeurs et tableaux c'est la partie la plus déroutante du langage C. Bien relire ton cours à ce sujet, faire des exercices. Forger ! 

      -
      Edité par rouIoude 8 juillet 2023 à 0:53:43

      • Partager sur Facebook
      • Partager sur Twitter
      ...
        8 juillet 2023 à 0:55:24

        Merci pour ta réponse!

        1) Je connaissais pas le data segment, je vais me renseigner merci!

        rouIoude a écrit:

        Dans ton exemple ce ne sont pas des chaînes que tu assignes, mais un pointeur auquel tu assignes l'adresse des chaînes

        Mais un tableau est un pointeur, non ? C'est un pointeur qui pointe vers son premier élément, pourquoi c'est autant contradictoire, enfin du moins je l'ai apprit comme ça

        2) Je ne savais pas du tout!! mais du coup ça m'embrouille encore plus, peux-tu me faire une démo de comment passer une string et seulement une string et pas le pointeur stp ?

        Ok j'ai compris en faite mais c'est pas très pro, non? enfin oui c'est des pro qui l'ont codé mais dans ce cas ça revient au même de faire ça

        char	*deletememory(char **ptr)
        {
        	if (*ptr) {
        		free(*ptr);
        		*ptr = NULL;
        	}
        	return ptr;
        }

        le return à la fin est vraiment pas utile

        3) Ok mais ce que j'entendais par la c'est plus

        void test(char s[])
        et
        void test(char *s[]) // pointeur sur s et non double tableau

        mais encore une fois j'ai pas trouvé l'exemple que j'avais du coup, peut etre que c'est moi qui suis fou

        EDIT: non ça les modifiaient bien dans la fonction mais pas en dehors, ce n'était pas une littéral mais une chaine allouée avec malloc

        En attendant merci pour t'es précisions!

        -
        Edité par NiksLeo 8 juillet 2023 à 0:56:45

        • Partager sur Facebook
        • Partager sur Twitter
          8 juillet 2023 à 1:01:11

          NiksLeo a écrit:

          Mais un tableau est un pointeur, non ? 

          Et non, c'est justement ce qui induit en erreur. Un tableau c'est une suite d'éléments contiguë en mémoire. Il a une adresse comme toute variable et elle est fixée des sa création (définition). Un pointeur c'est une variable destiner à contenir une adresse qui peut être l'adresse d'un tableau ou autre. Tu peux lui affecter une autre adresse si tu veux (à condition qu'elle soit du même type bien sur)..

          NiksLeo a écrit:

          2) Je ne savais pas du tout!! mais du coup ça m'embrouille encore plus, peux-tu me faire une démo de comment passer une string et seulement une string et pas le pointeur stp ?

          Une string, c'est un tableau de char avec le '\0' terminal, et comme je t'ai dit on ne peut pas passer un tableau directement à une fonction. Tu est obliger de passer par un pointeur, tu n'as pas le choix !



          -
          Edité par rouIoude 8 juillet 2023 à 1:05:20

          • Partager sur Facebook
          • Partager sur Twitter
          ...
            8 juillet 2023 à 1:21:20

            Oké... je sais plus dans quel cours j'ai vu ça mais ok merci à eux de m'avoir embrouiller et à toi de m'éclairer.

            Et pour le 2) je parlais du fait que tu pouvais le faire via une structure ?

            • Partager sur Facebook
            • Partager sur Twitter
              8 juillet 2023 à 2:36:28

              Pour ton erreur, voici comment faire:


               


              #include <stdio.h>


              int main(void) {


                  char new[100];


                  char old[100] = "abcdefg";


                  char *dst = new;


                  char *src = old;


                  while((*dst++ = *src++));


                  printf("%s\n", new);


              }


               


              old et new sont des tableaux et le compilateur requière des indices pour ceux-ci.


              Alors que src et dst sont des pointeurs vers le début de ces tableaux.


              Note qu'il n'est pas nécessaire de mettre 0 dans le dernier, il se met dans le while. C'est justement la condition de sortie.


              Une fonction ne fait pas la différence entre  int src[]  et  int *src  car elle attend un pointeur de toute façon.


              Tu peux écrire dans le code de la fonction  *src = ... ou bien src[indice] = ...

              -
              Edité par PierrotLeFou 8 juillet 2023 à 2:38:52

              • Partager sur Facebook
              • Partager sur Twitter

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

                8 juillet 2023 à 8:15:58

                Un point à savoir, c'est que quand on  déclare une fonction avec un paramètre "tableau"

                void foo(int t[100])
                {
                   ....
                }

                ça n'en n'a pas l'air, mais ça déclare t comme POINTEUR, pas comme un tableau.

                Donc dans le corps de la fonction, on pourra affecter une adresse à t.

                Ok, ça n'a pas l'air logique, mais c'est comme ça en C, et c'est un peu tard pour changer maintenant.

                En fait c'est pas juste qu'ils avaient fumé la moquette, c'est un compromis avec les origines de C (B, BCPL,...) quand les notions de tableau et de pointeurs n'étaient pas bien séparées (tableau = pointeur initialisé avec l'adresse d'une zone réservée par la même occasion).

                Et puis les (vrais) tableaux, avec

                int a[10], b[10];

                on ne peut pas faire l'affectation   a = b;   parce que les tableaux ne sont pas des VARIABLES (l-values).

                Pas une idée brillante, faut bien dire.

                Bref : si les tableaux en C vous confusionnent, c'est que vous êtes sains d'esprit. La conception du langage C  c'est un peu n'importe quoi. Il serait peut être temps de passer à autre chose, au lieu de s'accrocher à C comme une moule à la coque du Titanic.

                -
                Edité par michelbillaud 9 juillet 2023 à 9:04:21

                • Partager sur Facebook
                • Partager sur Twitter
                  9 juillet 2023 à 18:10:07

                  Ok merci vraiment parce-que pour moi un tableau était un pointeur vers son premier élément mais c'est faux

                  char p[] // tableau
                  char *s // pointeur
                  
                  char *ptr = p; // pointeur sur le premier élément

                  Et je ne savais pas du tout que quoi qu'il arrive il transformerait mon char [] en char *, mais dans ce cas écrire dans les paramètres de la fonction char dst[] est faux en soit il faudrait écrire avec * le compilo ne devrait pas l'accepter

                  Quand je repense a toute les personnes que j'ai "aider" en leur disant:

                  Mais non un tableau c'est pointeur !

                  - Ah c'est un pointeur ? Mais dans ce cas un pointeur est un tableau

                  - Mais nan, enfin non, ça dépend du contexte

                  La discussion interminable :lol::lol:

                  michelbillaud a écrit:

                  Bref : si les tableaux en C vous confusionnent, c'est que vous êtes sains d'esprit. La conception du langage C  c'est un peu n'importe quoi. Il serait peut être temps de passer à autre chose, au lieu de s'accrocher à C comme une moule à la coque du Titanic.

                   Ahaha ça me rassure! Et je le trouve top ce langage, moi ! Et puis depuis que je l'ai apprit passer sur des langages hauts niveaux c'est très facile.

                  Mais sinon, vous me conseillez quoi comme langage ?

                  • Partager sur Facebook
                  • Partager sur Twitter
                    9 juillet 2023 à 18:28:04

                    Un exemple : si un tableau était un pointeur, quelle serait sa taille ?

                    Code

                    #include <stdio.h>
                    
                    int main() {
                    	int tableau[10];
                    	int *pointeur;
                    	
                    	pointeur = tableau;    // tout à fait légal
                    
                    	printf("taille en octets : pointeur = %d, tableau = %d\n",
                    		   (int) sizeof(pointeur), (int) sizeof(tableau));
                    	return 0;
                    }
                    



                    Exécution

                    $ make a
                    cc     a.c   -o a
                    $ ./a
                    taille en octets : pointeur = 8, tableau = 40
                    



                    > Et puis depuis que je l'ai apprit passer sur des langages hauts niveaux c'est très facile.

                    Apprendre un langage de programmation est toujours BEAUCOUP plus facile quand on en connaît déjà un (*).  Parce que la difficulté principale, c'est pas les détails du premier langage, mais l'acquisition des compétences générales pour arriver à programmer (découper en sous-problèmes, arrêter d'imaginer que l'ordinateur va deviner ce qu'on veut faire, savoir chercher une erreur,  etc).

                    > Mais sinon, vous me conseillez quoi comme langage ?

                    Puisqu'on n'en n'est plus au premier langage, la question c'est : pour faire quoi ?

                    (*) en restant dans la même famille, parce que passer du procédural au paradigme fonctionnel ou logique, c'est une autre histoire.


                    • Partager sur Facebook
                    • Partager sur Twitter
                      9 juillet 2023 à 18:49:31

                      Un tableau est bien un tableau. Mais le nom du tableau correspond à un pointeur sur le premier élément, sauf dans les 3 contextes:

                      int  tab[] = {1,2,3,4};   // définition du tableau tab
                      sizeof(tab);              // retourne la taille du tableau
                      &tab;                     // est l'adresse du tableau (sa valeur est la même que tab, mais son type est int(*const)[4])

                      Dans toutes les autres expressions, tab a le type int*const et vaut l'adresse du premier élément

                         tab = {1,2,3,4};                // INVALIDE tab est int *const
                         int*  adr = tab;                // valide, adr est lui aussi l'adresse du 1er élément
                         tab[2];                         // <=>  *(tab+2)
                         printf( "%p\n", tab + 2 );      // indique adresse du 3ième élément du tableau
                      
                      void  fct( int adr[4] ) {          // attention adr est en fait un pointeur de type int*
                          printf( "%zd\n", sizeof(adr) );// indique la taille d'un pointeur
                          adr[2];                        // <=>  *(adr+2)
                          printf( "%p\n", adr + 2 );     // adresse du 3ième élément du tableau
                      }
                      
                         fct( tab );                     // c'est l'adresse du 1er élément qui est transmise, jamais le tableau

                      -
                      Edité par Dalfab 9 juillet 2023 à 18:50:02

                      • Partager sur Facebook
                      • Partager sur Twitter

                      En recherche d'emploi.

                        10 juillet 2023 à 1:38:19

                        Ce n'est peut-être pas une si mauvaise idée de continuer en C.
                        Toutes les conneries de ce langage, si tu les comprends, cela pourra t'aider à mieux comprendre le foncionnement d'un ordinateur.
                        Tu ne les verras plus dans des langages comme C++ ou Python.
                        Aujourd'hui, je pense qu'on devrait commencer la programmation avec le langage Python.
                        • Partager sur Facebook
                        • Partager sur Twitter

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

                          10 juillet 2023 à 10:39:13

                          NiksLeo a écrit:

                          (...)

                          char	*stringcopy(char dst[], char src[])
                          {
                          	while ((*dst++ = *src++))
                          		;
                          	*dst = 0;
                          	return dst;
                          }
                          
                          (...)

                          Et pourquoi en avoir fait un char * en tant que retour alors que le void fait très bien l'affaire ?


                          Certainement pour faire une fonction similaire à la fonction standard strcpy()

                          https://cplusplus.com/reference/cstring/strcpy/

                          dont le prototype est :

                          char * strcpy ( char * destination, const char * source );


                          L'idée est qu'en retournant aussi destination, on puisse chaîner l'appel à strcpy() pour l'utiliser en tant que paramètre dans l'appel d'une autre fonction.

                          • Partager sur Facebook
                          • Partager sur Twitter
                            11 juillet 2023 à 7:53:43

                            Dlks a écrit:

                            NiksLeo a écrit:

                            (...)

                            char	*stringcopy(char dst[], char src[])
                            {
                            	while ((*dst++ = *src++))
                            		;
                            	*dst = 0;
                            	return dst;
                            }
                            
                            (...)

                            Et pourquoi en avoir fait un char * en tant que retour alors que le void fait très bien l'affaire ?


                            Certainement pour faire une fonction similaire à la fonction standard strcpy()

                            https://cplusplus.com/reference/cstring/strcpy/

                            dont le prototype est :

                            char * strcpy ( char * destination, const char * source );


                            L'idée est qu'en retournant aussi destination, on puisse chaîner l'appel à strcpy() pour l'utiliser en tant que paramètre dans l'appel d'une autre fonction.


                            Dans Le K&R version 2 (après normalisation ansi C), apparaissent strlen et strcpy comme "useful functions adapted from the standard library". https://kremlin.cc/k&r.pdf page 104 et suivantes

                            > the first function strcpy(s, t), which copies the string t to the string s.

                            Ah ok, t = source, et s = target, ça commence fort.

                            Suivent plusieurs versions avec tableaux / avec pointeurs dans des formes détaillées comme

                            void strcpy(char *s, char *t)
                            {
                                 while ((*s = *t) != '\0) {
                                    s++;
                                    t++;
                                 }
                            }
                            

                            jusqu"à des formes compactes que ""the experienced C programmers would prefer",et même

                            void strcpy(char *s, char *t) 
                            {
                                while*s++ = * t++)
                                   ;
                            }
                            

                            qui "may seem cryptic at first sight".

                            Il est ensuite précisé que dans le standard strcpy "returns the target string as its function value".

                            (je ne sais pas pourquoi, j'ai l'impression que c'est écrit en poussant un soupir, genre "ça n'a pas de sens, ils sont un peu cons dans le comité de normalisation, mais bon c'est comme ça")

                            ----

                            Dans le K&R version 1, la fonction strcpy y est aussi p100 https://www.ccapitalia.net/descarga/docs/1978-ritchie-the-c-programming-language.pdf

                            (avec la même connerie de s pour target et t pour source)

                            strcpy(s, t) /* copy t to s; pointer version 3 */
                            char *s, *t;
                            {
                                  while (*s++ = *t++)
                                       ;
                            }
                            

                            mais aucun type de retour n'est déclaré (void n'existait pas, et par défaut c'est int) ni utilisé.

                            Au passage, remarquez l'ancienne façon de déclarer les types des paramètres.


                            ---

                            On peut imaginer qu'un clampin a eu l'idée que c'était malsain d'avoir dans une biblithèque des fonctions retournant une valeur indéfinie, et que ça si ça retournait l'adresse de la cible plutot que le pointeur nul, ça mangeait pas de pain. Le stagiaire a codé ce que le chef lui a dit de faire, et ça se retrouve dans la bibliothèque (où personne n'en fait rien), et puis après on fait passer en force (as we are the uncontested  leader in this industry) toute la bibliothèque au comité de normalisation, idées stupides compris.


                            ---

                            PS après quelques recherches on trouve la ligne

                            .B char *strcpy(s1, s2)

                            dans la page  de manuel string.3 https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/man/man3/string.3

                            du Manuel Unix V7, Bell Labs, 1979

                            Pas de page de manuel string.3 dans la V6 de 1975

                            Ca doit venir des Bell Labs, finalement.

                            ---

                            Ce qui aurait été moins débile, c'est de retourner l'adresse du caractère nul qu'on a mis dans la destination (ce que fait stpcpy). C'est plus utile que l'adresse de la destination, qu'on connaît forcément pour appeler strcpy.

                            PS: en regardant plus attentivement, c'est d'ailleurs ce qui passe dans le code donné plus haut

                            char	*stringcopy(char dst[], char src[])
                            {
                            	while ((*dst++ = *src++))
                            		;
                            	*dst = 0;
                            	return dst; /* adresse du terminateur ajouté */
                            }

                            alors que la version équivalente à strcpy serait

                            char	*strcpy(char dst[], char src[])
                            {
                                    char *ptr = dst;
                            	while ((*ptr++ = *src++))
                            		;
                            	*ptr = 0;
                            	return dst;   /* adresse de la chaine destination */
                            }




                            -
                            Edité par michelbillaud 11 juillet 2023 à 10:56:52

                            • Partager sur Facebook
                            • Partager sur Twitter
                              11 juillet 2023 à 11:15:34

                              Salut michelbillaud,

                              Merci pour cette excursion archéologique.

                              Choisir s et t pour des chaînes (string), qui sont des lettres consécutives de l'alphabet comme on choisit i et j pour des variables jetables pour deux entiers (integer) est effectivement maladroit. Cette confusion possible est absente dès le standard C89 qui utilise s1 et s2 dans la description du prototype : char *strcpy(char *s1, const char *s2);

                              Il n'en demeure pas moins que le fait que la fonction retourne l'adresse de la fonction de destination permet de chaîner l'appel à strcpy() pour l'utiliser en tant que paramètre dans l'appel d'une autre fonction, même si ce n'est pas utile très souvent et que cela correspond à un style de programmation qui n'est pas celui que je privilégie personnellement.

                              On peut imaginer un cas d'usage :

                              • d'un programme qui lit un fichier ligne par ligne dans un tampon "line" servant à la lecture dans la boucle
                              • qui alloue la mémoire nécessaire au stockage de cette ligne dans un emplacement pointé par "storage"
                              • et qui insère l'adresse mémoire de storage dans une structure de données après avoir copié line dans storage le tout en une seule ligne de façon compacte
                              • Partager sur Facebook
                              • Partager sur Twitter
                                11 juillet 2023 à 11:43:02

                                On peut imaginer qu'un clampin a eu l'idée que c'était malsain d'avoir dans une biblithèque des fonctions retournant une valeur indéfinie, et que ça si ça retournait l'adresse de la cible plutot que le pointeur nul, ça mangeait pas de pain.

                                Peut-être que le clampin a eu la prémonition du paradigme fonctionnel  ;)

                                • Partager sur Facebook
                                • Partager sur Twitter

                                Le crayon la gomme et le papier sont les meilleurs outils du programmeur !

                                  11 juillet 2023 à 12:08:22

                                  Je suis très sceptique sur l'utilité du chainage de strcpy  (*), faute d'imaginer des exemples convaincants (**)

                                  Parce que

                                  char array[10];
                                  
                                  strcpy(strcpy(array, "abc"), "def"));
                                  

                                  c'est idiot (copier "abc" ne sert à rien)

                                  et

                                  char s1[10], s2[10];
                                  
                                  strcpy(s1, strcpy(s2, "xyz"));
                                  
                                  

                                  ça n'apporte rien par rapport à

                                  strcpy(s2, "xyz");
                                  strcpy(s1, s2);
                                  

                                  On peut aussi penser à appeler strcpy dans les expressions fournies à un autre appel, mais bon, en plus d'être inutile, c'est quand même casse gueule dans un langage où l'ordre d'évaluation n'est pas spécifié (=> UB !)

                                  foo( strcpy(s, "abc"), strcpy(s, "def"));
                                  


                                  PS: en plus, mon moi d'une vie antérieure qui programmait en assembleur souffre à l'idée d'utiliser un registre (et des instructions supplémentaires)(***) dans strcpy pour sauvegarder l'ancienne valeur du pointeur dst pour pouvoir la réexpédier en résultat, dont probablement personne ne fera rien.

                                  PS2: le problème de nommage malheureux (pas pire que ce qui se faisait souvent en fortran à l'époque) était réglé dans la bibliothèque string.h d'ATT.  Mais K+R ont repris les exemples de la 1ere ed. (probablement développés dans des supports de cours antérieurs) en les adaptant vite fait à la nouvelle norme, pas aux bonnes pratiques.  Que ceux qui n'ont jamais loupé une occasion d'améliorer un polycop en le mettant à jour leur jettent la première pierre.



                                  (*) Alors que je suis un big fan du "fluent API design" en Java.

                                  (**) mais je suis curieux de voir des propositions (avec le code, parce que "ça pourrait servir", c'est pas suffisant, ça ne permet pas de comparer avec les alternatives).

                                  (***) je suis économe sur les ressources, pas radin.

                                  -
                                  Edité par michelbillaud 11 juillet 2023 à 12:21:12

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    11 juillet 2023 à 14:31:45

                                     Merci pour toutes ces explications préhistorique très utile

                                    michelbillaud a écrit:

                                    Un exemple : si un tableau était un pointeur, quelle serait sa taille ?

                                    Code

                                    #include <stdio.h>
                                    
                                    int main() {
                                    	int tableau[10];
                                    	int *pointeur;
                                    	
                                    	pointeur = tableau;    // tout à fait légal
                                    
                                    	printf("taille en octets : pointeur = %d, tableau = %d\n",
                                    		   (int) sizeof(pointeur), (int) sizeof(tableau));
                                    	return 0;
                                    }

                                     C'est vrai qu'avec cet exemple au moins c'est plus parlant!

                                    michelbillaud a écrit:

                                    > Et puis depuis que je l'ai apprit passer sur des langages hauts niveaux c'est très facile.


                                    Apprendre un langage de programmation est toujours BEAUCOUP plus facile quand on en connaît déjà un (*).  Parce que la difficulté principale, c'est pas les détails du premier langage, mais l'acquisition des compétences générales pour arriver à programmer (découper en sous-problèmes, arrêter d'imaginer que l'ordinateur va deviner ce qu'on veut faire, savoir chercher une erreur,  etc).

                                     C'est vrai, et je m'en suis bien rendu compte mais du coup je suis persuadé que si j'avais pas commencé par un langage tel que le C, j'aurais eu beaucoup moins d'aisance à passer sur d'autres langages

                                    PierrotLeFou a écrit:

                                    Aujourd'hui, je pense qu'on devrait commencer la programmation avec le langage Python.

                                     Du coup, c'est un peu contradictoire, non ? Vous conseillez pour ceux qui débutent aujourd'hui en programmation de commencer par Python or 

                                     PierrotLeFou a écrit:

                                    Toutes les conneries de ce langage, si tu les comprends, cela pourra t'aider à mieux comprendre le foncionnement d'un ordinateur.
                                    Tu ne les verras plus dans des langages comme C++ ou Python.



                                    ----------------

                                    michelbillaud a écrit:

                                    > Mais sinon, vous me conseillez quoi comme langage ?

                                    Puisqu'on n'en n'est plus au premier langage, la question c'est : pour faire quoi ?

                                    J'ai touché un peu aux applications web et j'avoue ne pas être un grand fan. J'ai touché au C# aussi et ça m'a beaucoup plus mais uniquement avec les WinForms. Et pour répondre à la question, je ne sais pas trop mais j'avoue que se faire à l'idée que je serai pas embauché pour faire du C plus tard m'attriste beaucoup car la plupart des entreprises aujourd'hui recherche plus des dev web et, ceux qui embauche les dev C c'est plus pour faire du C avec des maths or ce n'est pas du tout mon rayon, me voila bloquer entre rêve et réalité

                                    Dalfab a écrit:

                                    Un tableau est bien un tableau. Mais le nom du tableau correspond à un pointeur sur le premier élément, sauf dans les 3 contextes:

                                    int  tab[] = {1,2,3,4};   // définition du tableau tab
                                    sizeof(tab);              // retourne la taille du tableau
                                    &tab;                     // est l'adresse du tableau (sa valeur est la même que tab, mais son type est int(*const)[4])

                                    Dans toutes les autres expressions, tab a le type int*const et vaut l'adresse du premier élément

                                       tab = {1,2,3,4};                // INVALIDE tab est int *const
                                       int*  adr = tab;                // valide, adr est lui aussi l'adresse du 1er élément
                                       tab[2];                         // <=>  *(tab+2)
                                       printf( "%p\n", tab + 2 );      // indique adresse du 3ième élément du tableau
                                    
                                    void  fct( int adr[4] ) {          // attention adr est en fait un pointeur de type int*
                                        printf( "%zd\n", sizeof(adr) );// indique la taille d'un pointeur
                                        adr[2];                        // <=>  *(adr+2)
                                        printf( "%p\n", adr + 2 );     // adresse du 3ième élément du tableau
                                    }
                                    
                                       fct( tab );                     // c'est l'adresse du 1er élément qui est transmise, jamais le tableau

                                    Merci je suis maintenant de faire la distinction. 

                                    Dlks a écrit:

                                    NiksLeo a écrit:

                                    (...)

                                    char	*stringcopy(char dst[], char src[])
                                    {
                                    	while ((*dst++ = *src++))
                                    		;
                                    	*dst = 0;
                                    	return dst;
                                    }
                                    
                                    (...)

                                    Et pourquoi en avoir fait un char * en tant que retour alors que le void fait très bien l'affaire ?


                                    Certainement pour faire une fonction similaire à la fonction standard strcpy()

                                    https://cplusplus.com/reference/cstring/strcpy/

                                    dont le prototype est :

                                    char * strcpy ( char * destination, const char * source );


                                    L'idée est qu'en retournant aussi destination, on puisse chaîner l'appel à strcpy() pour l'utiliser en tant que paramètre dans l'appel d'une autre fonction.

                                    Oui merci mais je partage l'avis michelbillaud, je ne vois vraiment pas l'utilité et c'est pour ça que je comprenais pas pourquoi ce n'était pas void son type de retour.

                                    PierrotLeFou a écrit:

                                    Note qu'il n'est pas nécessaire de mettre 0 dans le dernier, il se met dans le while. C'est justement la condition de sortie.

                                     Exact! je n'ai pas fait attention

                                    michelbillaud a écrit:

                                    PS: en regardant plus attentivement, c'est d'ailleurs ce qui passe dans le code donné plus haut

                                    char	*stringcopy(char dst[], char src[])
                                    {
                                    	while ((*dst++ = *src++))
                                    		;
                                    	*dst = 0;
                                    	return dst; /* adresse du terminateur ajouté */
                                    }

                                    alors que la version équivalente à strcpy serait

                                    char	*strcpy(char dst[], char src[])
                                    {
                                            char *ptr = dst;
                                    	while ((*ptr++ = *src++))
                                    		;
                                    	*ptr = 0;
                                    	return dst;   /* adresse de la chaine destination */
                                    }

                                    C'est exact aussi! ce n'était pourtant pas voulu mais oui

                                    michelbillaud a écrit:

                                    Ce qui aurait été moins débile, c'est de retourner l'adresse du caractère nul qu'on a mis dans la destination (ce que fait stpcpy). C'est plus utile que l'adresse de la destination, qu'on connaît forcément pour appeler strcpy.

                                    Je suis totalement d'accord 👍

                                    PS: Merci d'avoir poussé plus loin que la question

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      11 juillet 2023 à 14:43:53

                                      michelbillaud a écrit:

                                      char	*stringcopy(char dst[], char src[])
                                      {
                                      	while ((*dst++ = *src++))
                                      		;
                                      	*dst = 0;
                                      	return dst; /* adresse du terminateur ajouté */
                                      }

                                      Pas tout à fait, car le '\0' est copié et dst pointe après le '\0'. Avec la fonction renvoyant l'adresse du '\0' terminal on aurait pu chaîner pour faire de la concaténation :

                                      #include <stdio.h>
                                      #include <string.h>
                                      
                                      char *stringcopy(char *dst, const char *src)
                                      {
                                          while(*src!='\0') *dst++ = *src++;
                                          *dst='\0';
                                          return dst;
                                      }
                                      
                                      int main(void)
                                      {
                                          char str[128];
                                      
                                          char *s1 = "un ";
                                          char *s2 = "deux ";
                                      
                                          stringcopy(stringcopy(str, s1), s2);
                                          puts(str);
                                      
                                          return 0;
                                      }

                                      Mais bon on est d'accord que ce n'est pas très utile.


                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                      ...
                                        11 juillet 2023 à 14:50:16

                                        NiksLeo a écrit:

                                        michelbillaud a écrit:

                                        > Et puis depuis que je l'ai apprit passer sur des langages hauts niveaux c'est très facile.


                                        Apprendre un langage de programmation est toujours BEAUCOUP plus facile quand on en connaît déjà un (*).  Parce que la difficulté principale, c'est pas les détails du premier langage, mais l'acquisition des compétences générales pour arriver à programmer (découper en sous-problèmes, arrêter d'imaginer que l'ordinateur va deviner ce qu'on veut faire, savoir chercher une erreur,  etc).

                                         C'est vrai, et je m'en suis bien rendu compte mais du coup je suis persuadé que si j'avais pas commencé par un langage tel que le C, j'aurais eu beaucoup moins d'aisance à passer sur d'autres langages

                                        Ah mais ça on peut être persuadé de ce qu'on veut, mais en commençant par Fortran et Pascal, je n'ai pas eu de mal à me mettre à Cobol (et gagner des sous avec pendant les vacances) et des tas d'autres trucs, dont C, plus tard.

                                        D'expérience, j'ai enseigné un certain temps (houlà) en IUT, et les étudiants qui avaient commencé par C avaient un fâcheuse tendance à se perdre dans des détails à la con, et à croire que les trucs obscurs avec lesquels ils se compliquaient la vie faisaient plaisir au compilateur, qui optimisait pour les remercier de la peine qu'ils avaient pris.

                                        (surtout le C enseigné par les profs de physique ou d'électronique, je dénonce personne mais bon., c'est presque aussi vilain que du Fortran écrit par des profs de maths)

                                        > la plupart des entreprises aujourd'hui recherche plus des dev web et, ceux qui embauche les dev C c'est plus pour faire du C avec des maths or ce n'est pas du tout mon rayon, me voila bloquer entre rêve et réalité

                                        Oui il y a une grosse niche pour le développement web et d'applis pour mobile. Sinon, une partie importante de ce qui se fait actuellement en C/C++ devrait se transférer sur Rust, qui donne un contrôle plus sérieux sur ce qu'on écrit, et évite des catastrophes.

                                        Pour code d'application de gestion, y en a toujours pour dire "ouin ouin, cobol ce vieux machin des années 60, ça fait 30 ans qu'on aurait dire réécrire tout ça dans quelque chose de plus moderne. Ben, on peut dire que ça ne serait pas une mauvaise chose pour les trucs en C. Et là on a mieux à proposer.

                                        (J'ai entendu dire que ça allait dans ce sens pour le code des robots footballeurs de la robocup, dont Bordeaux a ENCORE gagné la final en KidSize, y en a marre, c'est toujours les mêmes)

                                        https://www.youtube.com/watch?v=ULsAjGB2Kfk



                                        -
                                        Edité par michelbillaud 11 juillet 2023 à 14:52:37

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          11 juillet 2023 à 16:37:30

                                          Dlks a écrit:

                                          (...)

                                          On peut imaginer un cas d'usage :

                                          • d'un programme qui lit un fichier ligne par ligne dans un tampon "line" servant à la lecture dans la boucle
                                          • qui alloue la mémoire nécessaire au stockage de cette ligne dans un emplacement pointé par "storage"
                                          • et qui insère l'adresse mémoire de storage dans une structure de données après avoir copié line dans storage le tout en une seule ligne de façon compacte

                                          michelbillaud a écrit:

                                          Je suis très sceptique sur l'utilité du chainage de strcpy  (*), faute d'imaginer des exemples convaincants (**)

                                          (...)

                                          (*) Alors que je suis un big fan du "fluent API design" en Java.

                                          (**) mais je suis curieux de voir des propositions (avec le code, parce que "ça pourrait servir", c'est pas suffisant, ça ne permet pas de comparer avec les alternatives).

                                          Il me semble que le cas d'usage que je mentionne allait un peu plus loin que "çà pourrait servir".

                                          Si tu tiens à voir le code correspondant, cela pourrait donner ceci avec une fonction mettant des chaînes de caractères récupérées dans une boucle dans une file par exemple.

                                          #include <stdio.h>
                                          #include <stdlib.h>
                                          #include <string.h>
                                          
                                          struct queue {
                                                  /* TODO */
                                          };
                                          
                                          int queue_init(struct queue * q);
                                          int queue_add(struct queue * q, char * st);
                                          int queue_free(struct queue * q);
                                          
                                          int main(void) {
                                              FILE * fp;
                                              fp = fopen("read.txt","r");
                                              if (!fp)
                                                          return 1;
                                          
                                              char buffer[255];
                                              char * storage;
                                          
                                              struct queue q;
                                              if (!queue_init(&q))
                                                          return 1;
                                          
                                              while (fgets(buffer, 255, fp)) {
                                                  size_t len = strlen(buffer);
                                                  storage = malloc(len + 1);
                                                  if (!storage)
                                                          return 1;
                                                  if (!queue_add(&q, strcpy(storage, buffer)))
                                                          return 1;
                                              }
                                              fclose(fp);
                                          
                                              /* TODO: do something with the queue */
                                          
                                              if (!queue_free(&q))
                                                          return 1;
                                          
                                              return 0;
                                          }


                                          Le chaînage est ligne 31.

                                          -
                                          Edité par Dlks 11 juillet 2023 à 16:38:03

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            11 juillet 2023 à 18:08:32

                                            Ok, mais la supériorité de

                                                    if (!queue_add(&q, strcpy(storage, buffer)))
                                                            return 1;

                                            sur

                                                    strcpy(storage, buffer);
                                                    if (!queue_add(&q, storage))
                                                            return 1;


                                            ne me parait pas d'une évidence indiscutable. (*)

                                            D'autant qu'on a jugé raisonnable, juste au dessus, d'écrire

                                                    size_t len = strlen(buffer);
                                                    storage = malloc(len + 1);
                                            

                                            plutôt que de compacter en

                                                    storage = malloc(strlen(buffer) + 1);
                                            


                                            Coherence de style, toussa.

                                            (*) avant qu'on s'attire des remarques désagréables, il manquerait un free si le add échoue - en imaginant que ça peut se passer ailleurs que dans un main - , mais bon c'est pas le sujet

                                             Mais quand même, si on utilise un strdup (de posix ou réécrit maison), il apparaît que c'est  avant le queue_add qu'il faut préparer la copie, vu qu'on doit la désallouer en cas d'échec

                                              while (fgets(buffer, 255, fp)) {
                                                    // préparer 
                                                    char *copy = strdup(buffer);
                                                    if (!copy)
                                                            return 1;
                                                    // ajouter
                                                    if (!queue_add(&q, copy)) {
                                                            free(copy);
                                                            return 1;
                                                }

                                             Ca me parait plus logique de grouper allocation+copie d'un côté / ajout de l'autre, que  allocation  /  copie+ajout

                                            -
                                            Edité par michelbillaud 12 juillet 2023 à 8:27:11

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              13 juillet 2023 à 10:32:26

                                              Salut Michel,

                                              strdup() n'est pas dans la bibliothèque C standard.

                                              Je suis pas d'accord avec ton objection concernant la cohérence de style.

                                              Je ne suis pas partisan du compactage de code systématique (ou pourrait, d'ailleurs, à l'extrême, tout chaîner dans queue_add(), même l'allocation, puisque la fonction n'a besoin que d'une adresse mémoire).

                                              Par contre, si un compactage raisonnable permet de rendre le code plus expressif, en exprimant de façon concise une idée claire correspondant à mon algorithme comme "insère dans la file l'adresse mémoire de stockage après y avoir copié le contenu du buffer de travail" je ne suis pas contre, surtout si ce type de choses doit se répéter dans le code (par exemple si je copie certaines lignes dans une file et d'autres dans d'autres, en fonction de leur contenu).

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                13 juillet 2023 à 12:35:24

                                                > strdup pas standard

                                                strdup qui est dans les bibliothèques BSD, System V depuis des lustres, Posix (2008), XOPEN 5.0et débarque (enfin) dans le prochain standard C

                                                https://open-std.org/JTC1/SC22/WG14/www/docs/n3096.pdf

                                                sage décision du comité de normalisation prise en 2019 (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2353.htm)

                                                et si on tient absolument à tout récrire soi-même, c'est pas difficile

                                                char *strdup(const char *s) 
                                                {
                                                    char *r = malloc(strlen(s) + 1));
                                                    if (r) strcpy(r, s);
                                                    return r;
                                                }
                                                

                                                c'est une fonction très utile qui évite de se tartiner des séquences de strlen+malloc+strcpy partout dans le code chaque fois qu'on a besoin d'allouer une copie d'une chaine.

                                                Ici, l'action c'est : ajouter dans la file une copie de la ligne.

                                                Le découpage naturel, c'est de créer la copie, et de l'ajouter ensuite.


                                                > on pourrait, d'ailleurs, à l'extrême, tout chaîner dans queue_add(), même l'allocation, puisque la fonction n'a besoin que d'une adresse mémoire).

                                                Pour ça il faut faire l'impasse sur le traitement d'erreurs de malloc.  Parce que le NULL éventuellement retourné par malloc retourne NULL, il est expédié à strcpy pour qui c'est un comportement indéfini.

                                                #include <stdio.h>
                                                #include <string.h>
                                                
                                                int main() {
                                                	char *dst = NULL;
                                                
                                                	// ... supposons un dst = malloc(1000), qui échoue
                                                	//
                                                	strcpy(dst, "hello");
                                                	printf("-> %s\n", dst);
                                                	return 0;
                                                }
                                                $ make a
                                                cc     a.c   -o a
                                                $ ./a
                                                Erreur de segmentation
                                                
                                                

                                                Si on veut compacter, on a le même problème avec

                                                queue_add(&q, strdup(buffer));
                                                

                                                à moins de supposer que queue_add ne fait rien quand son second paramètre est nul.

                                                Bon, bref, pas d'exemple plus convaincant de la nécessité que strcpy retourne l'adresse de la destination ?


                                                -
                                                Edité par michelbillaud 13 juillet 2023 à 13:06:00

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  13 juillet 2023 à 13:14:24

                                                  strdup() est très commode comme tu le dis (il faut se rappeler de libérer la mémoire qu'il consomme, ce qui est un réflexe plus évident avec un malloc() et et que les débutants n'ont pas toujours), mais n'est pas dans le standard du C.

                                                  S'il débarque dans le prochain standard tant mieux. La plupart des professionnels qui codent en C dans des environnements contraints ou embarqués ne risquent pas de le voir de sitôt, certains étant toujours bloqués en C89.

                                                  En tout état de cause ce n'est pas le sujet auquel je tente de répondre dans ma contribution à ce fil en réponse à la question de l'OP "Et pourquoi en avoir fait un char * en tant que retour alors que le void fait très bien l'affaire ?".

                                                  Si tu n'es pas "convaincu" par mon exemple, je n'y peux rien (les goûts et les couleurs...), c'est un exemple basé sur un cas d'application réel, et je crois en avoir objectivement motivé l'usage d'une façon qui répond à la question posée.

                                                  Le fait que strcpy() retourne l'adresse de destination n'est pas une "nécessité". Cela peut être utile, et je crois avoir expliqué comment.

                                                  Tu as le droit de t'en poser d'autres, mais je ne suis pas intéressé par discuter d'autres sujets ni de discuter de la pertinence du standard, ou de savoir si cela est le résultat d'une idée saugrenue d'un "stagiaire" comme tu as pu l'affirmer.

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    13 juillet 2023 à 13:47:04

                                                    C'est pourtant intéressant de comprendre pourquoi, à un moment de l'histoire, on a choisi d'orienter le standard dans telle ou telle direction. Parce qu'on en subit les conséquences ensuite.

                                                    Dans le dépôt du "working group" de C, il y a un document "rationale" qui marque le passage du

                                                    • C pour les vrais programmeurs avec des poils sous les bras (*) qui savent ce qu'ils font et le compilateur doit obéir quoi qu'ils disent de faire
                                                    • À un truc plus civilisé où les pointeurs rentrent pas forcement dans les entiers etc, et où le compilateur est là pour signaler le code chelou
                                                    Probablement que le problème c'est que les fonctions comme strdup, ça appelle malloc l'allocateur standard, et ça prive de la liberté d'employer un allocateur différent.
                                                    ---
                                                    pour le "ça peut être utile", ben, le problème c'est que là c'est plutôt un gadget. Certes ça permettrait de transformer
                                                    if (r) strcpy(r, s);
                                                    return r;
                                                    
                                                    
                                                    en
                                                    return r ? strcpy(r, s) : r;
                                                    
                                                    avec ça on est bien avancés.
                                                    ---

                                                    (*) pas comme les mangeurs de quiches qui font du Pascal surveillés par un compilateur fasciste 

                                                    -
                                                    Edité par michelbillaud 13 juillet 2023 à 16:12:59

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      14 juillet 2023 à 17:28:48

                                                      Suite aux discussions sur "strcpy retourne l'adresse de la destination parce que euh, des fois ça pourrait servir", je me suis amusé à regarder dans les sources d'un gros logiciel qui traite du texte, les compilateurs gcc, pour voir ce qu'il en était exactement.

                                                      TL;DR Conclusion : Les sources de gcc ne contiennent qu'un nombre insignifiant d'utilisation de la valeur de retour de strcpy(), et ces utilisations peuvent être très facilement évitées. Il n'y a aucune utilisation directe du résultat de strcpy() dans les paramètres d'un autre appel.

                                                      Objectif

                                                      La fonction strcpy (string copy) de la bibliothèque C

                                                      char *strcpy(char *restrict dst, const char *restrict src);
                                                      

                                                      copie la chaîne située à l'adresse src à l'adresse pointée par dst.

                                                      Elle retourne aussi l'adresse de la destination. On se demande si c'est vraiment utile, étant donné que cette adresse est celle indiquée par dst.

                                                      La rumeur dit que que cela permet de chaîner l'appel, c'est-à-dire d'utiliser le résultat de strcpy dans une autre opération.

                                                      Un exemple on peut imaginer vouloir faire quelque chose comme

                                                      char *copy = strcpy(malloc(strlen(src) + 1));
                                                      

                                                      pour allouer une copie d'une chaine src existante.

                                                      Mais ce n'est pas satisfaisant : malloc() peut retourner un pointeur nul en cas d'échec de l'allocation, et le comportement de strcpy avec un paramètre NULL est indéfini.

                                                      Donc en pratique on ferait d'abord le malloc(), puis la copie si il a réussi, soit

                                                      char *copy = malloc(strlen(src) + 1));
                                                      if (copy) {
                                                         strcpy(copy, src);
                                                      }
                                                      

                                                      et là, on dispose de l'adresse de la copie dans la variable avant d'avoir fait le strcpy(), dont on n'utilise en pratique pas le résultat.

                                                      Donc on se pose la question de l'utilité réelle d'avoir une valeur de retour pour strcpy(). Certes on peut employer la valeur retournée par strcpy(),mais en pratique, le fait-on de façon significative ? Aurait-on réellement du mal à s'en passer ?

                                                      Pour essayer d'y répondre, on regarde les utilisations concrètes de strcpy dans les sources d'un gros logiciel : le compilateur gcc.

                                                      Récupération des sources

                                                      Le compilateur gcc, avec ses 4,3Go de code, ne pourra sans doute pas être considéré comme un exemple anecdotique.

                                                      On le récupère sur le dépôt https://github.com/gcc-mirror/gcc.git

                                                      $ git clone https://github.com/gcc-mirror/gcc.git
                                                      Clonage dans 'gcc'...
                                                      remote: Enumerating objects: 2891437, done.
                                                      remote: Counting objects: 100% (3035/3035), done.
                                                      remote: Compressing objects: 100% (1403/1403), done.
                                                      remote: Total 2891437 (delta 1684), reused 2780 (delta 1610), pack-reused 2888402
                                                      Réception d'objets: 100% (2891437/2891437), 3.19 Gio | 9.93 Mio/s, fait.
                                                      Résolution des deltas: 100% (2345351/2345351), fait.
                                                      Mise à jour des fichiers: 100% (126025/126025), fait.
                                                      $ du -sh gcc
                                                      4,3G	gcc
                                                      

                                                      Examen

                                                      Il comporte à ce jour[^1]52284 sources C (portant le suffixe .c) et 3943 fichiers d'entête (suffixe .h)

                                                      [^1]: 14 juillet 2023, vers 12h heure de Paris.

                                                      Usage de strcpy() dans les fichiers d'entête

                                                      L'outil de base est la commande grep, jointe à find et xargs, qui permettent de l'appliquer à un grand nombre de fichiers.

                                                      En faisant[^2]

                                                      $ find gcc -name *.h -type f | xargs grep strcpy | wc -l
                                                      68
                                                      

                                                      On trouve 68 occurrences de la chaîne "strcpy".

                                                      [^2]: L'option -type f évite des messages d'erreurs causés par deux répertoires de gcc/libstdc++-v3/testsuite portant le suffixe ".h" : 26_numerics/headers/complex.h et 29_atomics/headers/stdatomic.h.

                                                      Un premier coup d'oeil sur le début des résultats

                                                      $ find gcc -name *.h -type f | xargs grep strcpy | head -n 10
                                                      gcc/gcc/rtl.h:    null byte of the string, e.g. strcpy
                                                      gcc/gcc/ada/adaint.h:#define xstrdup(S)  strcpy ((char *) malloc (strlen (S) + 1), S)
                                                      gcc/gcc/cp/cfns.h:      {"strcpy", 89},
                                                      gcc/gcc/config/epiphany/epiphany.h:	      strcpy (dst_name, prefix);				\
                                                      gcc/gcc/config/mips/mips.h:   cause character arrays to be word-aligned so that `strcpy' calls
                                                      gcc/gcc/config/mips/mips.h:   character arrays to be word-aligned so that `strcpy' calls that copy
                                                      gcc/gcc/config/rs6000/xcoff.h:  strcpy (buffer, NAME);						\
                                                      gcc/gcc/config/xtensa/xtensa.h:   cause character arrays to be word-aligned so that 'strcpy' calls
                                                      gcc/gcc/config/i386/i386.h:   cause character arrays to be word-aligned so that `strcpy' calls
                                                      gcc/gcc/config/i386/xm-djgpp.h:    strcpy (xref_file, file); \
                                                      

                                                      permet de voir que la chaîne figure dans les commentaires, dans les noms d'autres fonctions etc.

                                                      On peut essayer de réduire ces parasites, en cherchant strcpy suivi par une parenthèse ouvrante, avec éventuellement des espaces, ce qui ne fournit plus que 35 lignes

                                                      $ find gcc -name *.h -type f | xargs grep 'strcpy[[:space:]]*(' 
                                                      gcc/gcc/ada/adaint.h:#define xstrdup(S)  strcpy ((char *) malloc (strlen (S) + 1), S)
                                                      gcc/gcc/config/epiphany/epiphany.h:	      strcpy (dst_name, prefix);				\
                                                      gcc/gcc/config/rs6000/xcoff.h:  strcpy (buffer, NAME);						\
                                                      gcc/gcc/config/i386/xm-djgpp.h:    strcpy (xref_file, file); \
                                                      gcc/gcc/config/i386/xm-djgpp.h:      strcpy (t, xref_ext); \
                                                      gcc/gcc/config/darwin.h:    strcpy (buffer_, stub_);					\
                                                      gcc/gcc/config/darwin.h:	strcpy (buffer_ + (STUB_LENGTH) - 1, "_binder\"");	\
                                                      gcc/gcc/config/darwin.h:	strcpy (buffer_ + (STUB_LENGTH), "_binder");		\
                                                      gcc/gcc/config/darwin.h:	strcpy (buffer_, symbol_);				\
                                                      gcc/gcc/config/darwin.h:        strcpy (buffer_, "\"L");				\
                                                      gcc/gcc/config/darwin.h:        strcpy (buffer_ + 2, symbol_ + 1);			\
                                                      gcc/gcc/config/darwin.h:	strcpy (buffer_ + (SYMBOL_LENGTH), "$lazy_ptr\"");	\
                                                      gcc/gcc/config/darwin.h:        strcpy (buffer_, "\"L");				\
                                                      gcc/gcc/config/darwin.h:        strcpy (buffer_ + 2, symbol_);				\
                                                      gcc/gcc/config/darwin.h:	strcpy (buffer_ + (SYMBOL_LENGTH) + 2, "$lazy_ptr\"");	\
                                                      gcc/gcc/config/darwin.h:        strcpy (buffer_, "L");					\
                                                      gcc/gcc/config/darwin.h:        strcpy (buffer_ + 1, symbol_);				\
                                                      gcc/gcc/config/darwin.h:	strcpy (buffer_ + (SYMBOL_LENGTH) + 1, "$lazy_ptr");	\
                                                      gcc/gcc/m2/mc-boot/Glibc.h:EXTERN void * libc_strcpy (void * dest, void * src);
                                                      gcc/gcc/m2/pge-boot/Glibc.h:EXTERN void * libc_strcpy (void * dest, void * src);
                                                      gcc/gcc/testsuite/gcc.dg/builtin-object-size-common.h:  extern char *strcpy (char *, const char *);
                                                      gcc/gcc/testsuite/gcc.dg/strlenopt.h:char *strcpy (char *__restrict, const char *__restrict);
                                                      gcc/gcc/testsuite/gcc.dg/strlenopt.h:strcpy (char *__restrict dest, const char *__restrict src)
                                                      gcc/gcc/testsuite/gcc.dg/Wobjsize-1.h:__attribute__ ((__nothrow__)) strcpy (char *__restrict __dest, __const char *__restrict __src)
                                                      gcc/gcc/testsuite/gcc.dg/guality/guality.h:	  strcpy (buf, guality_gdb_command);
                                                      gcc/gcc/testsuite/gcc.dg/guality/guality.h:      strcpy (buf, guality_gdb_command);
                                                      gcc/gcc/testsuite/jit.dg/harness.h:  strcpy (result, prefix);
                                                      gcc/gcc/testsuite/jit.dg/harness.h:  strcpy (result + strlen (prefix), suffix);
                                                      gcc/gcc/testsuite/g++.dg/ipa/pr64049.h:		__builtin_strcpy (t, val);
                                                      gcc/gcc/testsuite/gcc.c-torture/execute/builtins/chk.h:#define strcpy(dst, src) \
                                                      gcc/gcc/testsuite/gcc.c-torture/execute/builtins/chk.h:#define __builtin_strcpy(dst, src) strcpy (dst, src)
                                                      gcc/libgfortran/libgfortran.h:extern gfc_charlen_type fstrcpy (char *, gfc_charlen_type, const char *, gfc_charlen_type);
                                                      gcc/libgfortran/libgfortran.h:extern gfc_charlen_type cf_strcpy (char *, gfc_charlen_type, const char *);
                                                      gcc/libssp/ssp/string.h:#define strcpy(dest, src) \
                                                      gcc/libvtv/vtv_set.h:  { return strcpy (buf, s) + strlen (s); }
                                                      

                                                      La seconde ligne xstrdup pose exactement le problème indiqué plus haut :malloc() peut retourner NULL, ce qui causera un crash.

                                                      Il n'y a qu'un endroit où on utilise manifestement le résultat de strcpy : à la dernière ligne. Le fichier <https:> est en fait une entête C++, qui n'est incluse que `gcc/libvtv/vtv_rts.cc.

                                                      struct insert_only_hash_sets_logger
                                                      {
                                                        // ...
                                                      
                                                        static char *
                                                        log (const char *s, char *buf)
                                                        { return strcpy (buf, s) + strlen (s); }
                                                      
                                                        // ...
                                                      }
                                                      

                                                      L'appel se fait dans la fonction membre log d'une définition de fonction statique dans une structure/classe. Il pourrait sans problème être remplacé par

                                                      static char *
                                                        log (const char *s, char *buf)
                                                        { 
                                                          strcpy (buf, s);
                                                          return buf + strlen (s); 
                                                        }
                                                      

                                                      Examen des sources C

                                                      Si on repart sur la base des commandes précédentes, on a trop de résultats

                                                      find gcc -name "*.c" -type f | xargs grep 'strcpy[[:space:]]*(' | wc -l
                                                      1155
                                                      

                                                      pour pouvoir les examiner tous "manuellement". Mais on voit rapidement que beaucoup de fichiers viennent des répertoires "testsuite"

                                                      $ find gcc -name testsuite
                                                      gcc/libffi/testsuite
                                                      gcc/gcc/testsuite
                                                      gcc/libatomic/testsuite
                                                      gcc/libitm/testsuite
                                                      gcc/libiberty/testsuite
                                                      gcc/libstdc++-v3/testsuite
                                                      gcc/libphobos/testsuite
                                                      gcc/libvtv/testsuite
                                                      gcc/libgo/testsuite
                                                      gcc/libgomp/testsuite
                                                      $ find gcc/*/testsuite -name "*.c"  | wc -l
                                                      50316
                                                      

                                                      qui ne contiennent pas du code représentatif de l'usage de C, mais des petites séquences destinées à vérifier le bon fonctionnement du compilateur.

                                                      On va donc ignorer les fichiers de ces répertoires. C'est l'occasion d'apprendre à utiliser l'option "-prune" de la commande "find".

                                                      find gcc  -type d -name testsuite -prune -o -name "*.c" -type f -print
                                                      

                                                      qui se lit :

                                                      • visiter le répertoire gcc,
                                                      • quand on trouve un répertoire nomme testsuite on l'ignore,
                                                      • quand on trouve un fichier avec le suffixe ".h", on affiche son nom,

                                                      et dont on fait un alias

                                                      alias sources='find gcc  -type d -name testsuite -prune -o -name "*.c" -type f -print'
                                                      

                                                      Maintenant, nous n'avons plus que

                                                      $ sources | wc -l
                                                      1968
                                                      

                                                      fichiers C à considérer avec

                                                      sources |  xargs grep "strcpy[[:space:]]*(" | wc -l
                                                      242
                                                      

                                                      mentions de strcpy() suivi d'une parenthèse.

                                                      utilisation avec return

                                                      Il n'y a que deux fichiers où strcpy est combiné avec return

                                                      $ sources |  xargs grep "strcpy[[:space:]]*(" | grep return 
                                                      gcc/gcc/m2/mc-boot-ch/Glibc.c:  return strcpy (dest, src);
                                                      gcc/libiberty/simple-object.c:    return strcpy (newname, name);
                                                      gcc/libiberty/simple-object.c:    return strcpy (newname, name);
                                                      gcc/libiberty/simple-object.c:    return strcpy (newname, name);
                                                      gcc/libiberty/simple-object.c:    return strcpy (newname, name);
                                                      gcc/libiberty/simple-object.c:    return strcpy (newname, name);
                                                      gcc/libiberty/simple-object.c:    return strcpy (newname, name);
                                                      
                                                      1. Dans le premier <https:> (bibliothèque C pour Modula 2) il s'agit de fournir une fonction libc_strcpy() semblable à strcpy() :
                                                      EXTERN
                                                      char *
                                                      libc_strcpy (char *dest, char *src)
                                                      {
                                                        return strcpy (dest, src);
                                                      }
                                                      
                                                      1. Dans le second <https:> c'est un aiguillage
                                                        else if (strcmp (name, ".note.GNU-stack") == 0)
                                                          return strcpy (newname, name);
                                                        else if (strcmp (name, ".note.gnu.property") == 0)
                                                          return strcpy (newname, name);
                                                        ...
                                                        else if (strcmp (name, ".BTF") == 0)
                                                          return strcpy (newname, name);
                                                      

                                                      avec 6 branches ayant un corps identique, qui pourrait avantageusement être remplacé par une condition composée

                                                        else if (strcmp (name, ".note.GNU-stack")    == 0)
                                                             || (strcmp (name, ".note.gnu.property") == 0)
                                                             || (strcmp (name, ".BTF")               == 0)))
                                                          return {
                                                      	   strcpy (newname, name);
                                                      	   return newname;
                                                      	}
                                                      

                                                      utilisation avec affectation

                                                      Si on cherche les lignes qui contiennent l'affectation du résultat de strcpy() en sélectionnant celles qui contiennent "=" :

                                                      $ sources |  xargs grep "strcpy[[:space:]]*(" | grep =
                                                      gcc/gcc/ada/adaint.c:  strcpy (encoding, "encoding=utf8");
                                                      gcc/libdecnumber/decCommon.c:    if ((i+1)%4==0) {strcpy(&amp;hexbuf[j], " "); j++;}
                                                      gcc/libdecnumber/decCommon.c:     strcpy(qbuf, "q=");
                                                      gcc/libgo/go/regexp/testdata/testregex.c:	
                                                      

                                                      on rentre bredouille.

                                                      utilisation du résultat de strcpy() dans un autre appel

                                                      On cherche maintenant l'utilisation d'un appel de strcpy() dans l'appel d'une autre fonction. La ligne contiendrait quelque chose comme

                                                           ....   foo(.... strcpy(.....) ....)
                                                      

                                                      On ratisse plus large en cherchant, sur une ligne, une parenthèse ouvrante, puis des caractères quelconques avant "strcpy", des espaces et une parenthèse ouvrante.

                                                      Là encore :

                                                      $ sources |  xargs grep "(.*strcpy[[:space:]]*(" 
                                                      gcc/libdecnumber/decCommon.c:    if ((i+1)%4==0) {strcpy(&amp;hexbuf[j], " "); j++;}
                                                      

                                                      on rentre bredouille.

                                                      Conclusion

                                                      Les sources de gcc ne contiennent qu'un nombre insignifiant d'utilisation de la valeur de retour de strcpy(), et ces utilisations peuvent être très facilement évitées.

                                                      Il n'y a aucune utilisation directe du résultat de strcpy() dans les paramètres d'un autre appel.

                                                      -
                                                      Edité par michelbillaud 14 juillet 2023 à 17:42:10

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        15 juillet 2023 à 17:46:21

                                                        Bon je pense qu'on pas faire plus clair, merci à tous de m'avoir répondu et éclairé.

                                                        Je pensais vraiment que le type de retour char * était la pour quelque chose d'autres que " parfois son retour est utile " et qu'un void serait plus adapté ou bien retourné un pointeur sur \0 (pour ma part) pour éviter un strlen à répétition plus tard.

                                                        Merci d'y avoir investit autant de temps!

                                                        Bien à vous

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          15 juillet 2023 à 18:48:58

                                                          L'idée d'aller regarder dans les sources de gcc m'a été soufflée en lisant les propositions pour l'inclusion de strdup en 2019, dans les discussions du comité de normalisation de C

                                                          https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2353.htm


                                                          As a data point, not counting tests, the latest GCC source tree contains 534 calls to such functions, the Binutils/GDB tree 760 of them, and the Linux kernel tree 1074.

                                                          Il y a aussi dans le document d'à côté

                                                          https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2352.htm

                                                          une proposition pour ajouter stpcpy, qui fait (en plus efficace)   strcpy(dst, src) + strlen(src);  et qui existe dans POSIX de longue date.   (Première mention historique : Lattice C AmigaDOS  en 1986 ! extension GNU depuis 1992...)

                                                          (apparemment cette proposition n'est pas passée, ça serait intéressant de comprendre pourquoi)

                                                          Et une discussion plus générale sur "Toward more efficient string copying and concatenation"

                                                          https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm

                                                          où il est expliqué que le retour d'un pointeur par <tt>strcat</tt>, <tt>strncat</tt>, <tt>strcpy</tt> et <tt>strncp</tt>, c'est un accident historique apparu dans la 7e édition d'Unix en 1978, que cette version utilisait abondamment des appels à ces 4 fonctions, mais qu"aucun appel n'utilisait la valeur de retour.

                                                          L'ennui, c'est que si on se met à faire des trucs comme strcat(dst, src) + strlen(src),  ca introduit un problème potentiel de performances parce qu'en principe, ça fait 1 parcours de chaîne supplémentaire pour le strlen. Pareil pour le chaînage avec strcat. Sauf si le compilateur sait reconnaître ce cas de figure et l'optimiser, ce qui n'est pas toujours le cas. D'où l'intérêt de strcpy et autres qui retournent l'adresse du caractère nul plutôt que de la destination.



                                                          <<

                                                          Specifically, the optimal complexity of the concatenation into the array <tt>d</tt> of N strings, S1 through SN with lengths L1 through LN is:

                                                          O (L1 + L2 + L3 + ... + LN)

                                                          but the complexity of a chain of calls

                                                          	strcat (... (strcat (strcpy (d, S1), S2), ... ), SN)
                                                          approaches quadratic because each subsequent <tt>strcat</tt>call must first traverse all the characters copied by the call before it:

                                                          O (N × L1 + (N − 1) × L2 + (N − 2) × L3 + ... + LN)

                                                          >>

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            16 juillet 2023 à 1:26:37

                                                            La remarque au sujet de strcat est tout à fait pertinente.
                                                            J'ai déjà dû utiliser des strcat de façon massive dans un code. J'ai préféré utiliser la variante explicite de strcpy suivante:
                                                                while((*dst++ = *src++));
                                                                dst--;   // Pour se ramener sur le '\0'
                                                            dst se trouve au bon endroit pour ajouter une autre chaîne. Ce qui est moins long que de faire des parcours inutiles ou des strlen inutiles.
                                                            Ou comme il a été dit, le temps d'exécution devient quadratique.
                                                            • Partager sur Facebook
                                                            • Partager sur Twitter

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

                                                              16 juillet 2023 à 14:04:25

                                                              Il semble que l'accident historique soit le suivant

                                                              La raison probable (ma théorie)
                                                              • les conventions d'appel choisies pour l'implémentation sur le PDP-11 font que le même registre est utilisé pour le passé du premier paramètre et la valeur de retour
                                                              • de la façon dont elle est implémentée sur ce système  la fonction strcpy ne nécessite pas de modifier le contenu de ce registre
                                                              • donc "ça coûte rien' de dire qu'en plus de faire son boulot normal, elle retourne un truc, au cas où quelqu'un voudrait en faire quelque chose

                                                              Le code est dans  usr/src/libc/gen/strcpy.c
                                                              /*
                                                               * Copy string s2 to s1.  s1 must be large enough.
                                                               * return s1
                                                               */
                                                              
                                                              char *
                                                              strcpy(s1, s2)
                                                              register char *s1, *s2;
                                                              {
                                                              	register char *os1;
                                                              
                                                              	os1 = s1;
                                                              	while (*s1++ = *s2++)
                                                              		;
                                                              	return(os1);
                                                              }
                                                              
                                                              Il est bien possible que les directives register indiquent au compilateur de laisser le registre "1ere parametre" tranquille (avec old s1) et d'utiliser un autre (registre de travail) pour la boucle. Ca coute juste une affectation de registre à registre.
                                                              On retrouve la même manip  (os1 = s1; ..... return s1) dans  strcat.c strncpy.c

                                                              -
                                                              Edité par michelbillaud 16 juillet 2023 à 14:10:42

                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              Quelques petites questions

                                                              × 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