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;
}
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
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.
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 !
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
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 !
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
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
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 ?
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.
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
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.
Le Tout est souvent plus grand que la somme de ses parties.
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.
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
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")
(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.
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
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
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
Le crayon la gomme et le papier sont les meilleurs outils du programmeur !
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
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
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 *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
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 :
> 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)
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;
}
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
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).
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
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.
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
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
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
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.
[^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
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.
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
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.
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
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.
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
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.
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"
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:
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.
Le Tout est souvent plus grand que la somme de ses parties.
elle est déclarée avec le type de retour char *, mais la valeur de retour n'est jamais utilisé dans les sources
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
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.
Le Tout est souvent plus grand que la somme de ses parties.
En recherche d'emploi.
Le Tout est souvent plus grand que la somme de ses parties.
Le crayon la gomme et le papier sont les meilleurs outils du programmeur !
Le Tout est souvent plus grand que la somme de ses parties.