1

Lorsque vous déclarez des fonctions en C qui traitent de chaine de caractères, faites vous
extern void f(const char *str, char *dest);

ou préférez vous comme prototype :
extern void f(const char str[], char dest[]);

et pourquoi ? (C'est 100% équivalent)

Même question sur autre chose que des tableaux de char .

2

Je n'utilise jamais la notation tableau dans les arguments de fonction en C. J'ai pas de raison particulière pour ça, c'est juste que la plupart des sources sont écrites avec des pointeurs (du moins celles que j'ai vues), donc j'ai pas le réflexe de faire autrement. Peut-être que le fait d'avoir fait de l'assembleur avant de débuter le C fait pencher la balance du côté des pointeurs, aussi cheeky
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

3

const char* s.

Pourquoi ? Je ne sais pas trop, l'habitude sans doute... Et c'est aussi plus rapide à taper. Et plus courant (?).
Pourtant c'est un peu idiot, parce que [] suggère qu'il y a plusieurs éléments chaînés, alors que * non.

4

Pen^2 (./3) :
const char* s.
Ça par contre, c'est pas bien, à moins que tu puisses jurer que jamais, jamais tu ne déclareras plus d'une seule variable en une fois grin
(bon pour les fonctions c'est pas un problème, mais pour les variables locales ou globales c'est un piège classique)

Autre chose : est-ce que ça marche aussi quand il y a des attributs après le nom, du genre const char str[] volatile ?
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

5

Zerosquare (./4) :
Ça par contre, c'est pas bien, à moins que tu puisses jurer que jamais, jamais tu ne déclareras plus d'une seule variable en une fois
Je le jure ! embarrassed
(OK c'est un piège à noob, mais de toute façon c'est moche — mais oui, j'avoue que c'est trompeur — mais char *s est moche — oh, je tourne en rond)

6

extern void f(const char* str, char* dest); pour moi. A cause de l'assembleur probablement, un pointeur ça existe en vrai, alors que quand on parle de tableau on est déjà dans un niveau d'abstraction, et je suis pas très doué à ça ^^


Bon, après, pour déclarer et définir une variable, j'utilise [] :
const char truc [] = "azemrkj"
Parce que sinon sur TI, ça ferait un relogement embarrassed

7

Personellement c'est [] car même si c'est équivalent, c'est ce qui représente le plus précisément la réalité. Dans la logique du programme, une chaine de caractère est un tableau de caractères pas un pointeur vers un caractère.

Après C est un langage sale qui mélange complètement les notions de pointeur et tableaux. Pour moi il aurait fallu imposer un cast pour faire les conversion de pointeur en tableau et inversement.

A une époque je faisais avec * car je tournais a l'économie, mais l'experience m'a appris a préférer le code propre au code court.
avatar

8

Uther (./7) :
C est un langage sale qui mélange complètement les notions de pointeur et tableaux

Les notions sont liées plutôt que mélangées. Ce sont deux angles de vue de la même réalité. C'est pas vraiment ma définition du mot sale, mais je pense qu'on fera un topic à côté pour en parler cheeky

9

PpHd (./1) :
extern void f(const char *str, char *dest);


Je prends aussi cette version là parce qu'avec un tableau, je me dirais qu'on n'a pas forcément mis le '\0' à la fin...
Même si c'est con vu qu'on passe pas la taille des chaines...

10

A mon tour !

Par habitude (et parce que la plupart du code existant utilise cette forme), j'utilise const char * + char * pour les prototypes de fonction.
Mais je commence à me demander s'il vaut mieux pas utiliser const char [] et char [], pour les raisons que Uther évoque: c'est plus propre, car le prototype de la fonction explicite le fait qu'il attend un tableau et non pas juste un caractère.
Exemple, dans la fonction suivante, on sait de suite quel est le tableau et quelle est la référence :
const int *f(int, const int [], const int *);

Mais çà a des dangers, car même si on écrit
void f(const char str[])
Le type effectif, vu par le compilateur, de str est en fait... const char * ! (cf. http://c-faq.com/aryptr/aryptrparam.html )
Exemple:
void f(const char str[]) {
 printf("Size=%d\n", (int) sizeof(str));
}

affiche Size=8 (taille d'un pointeur sur une architecture 64 bits). Encore plus troublant:
 void f(const char str[1024]) {
 printf("Size=%d\n", (int) sizeof(str));
}

affiche encore Size=8 ! (cf. http://c-faq.com/aryptr/aryparmsize.html )

11

PpHd (./10) :
car le prototype de la fonction explicite le fait qu'il attend un tableau et non pas juste un caractère

bon, moi j'abandonne ici, car c'est une pure vue de l'esprit cette vision des choses, donc je suis perdu, ou du moins ça me passe au-dessus grin d'autant que ça "tendrait à indiquer", mais rien ne certifie que const int [] n'attend pas au final qu'un caractère, donc bon, c'est le genre de "sécurité" étonnante, à moins qu'on fasse une confiance aveugle au code des autres grin)

• Folco out

12

Folco (./11) :
c'est une pure vue de l'esprit cette vision des choses

Tout à fait oui
d'autant que ça "tendrait à indiquer", mais rien ne certifie que const int [] n'attend pas au final qu'un caractère,

Ca attend un tableau, qui peut n'être que de un élément.

13

Bon, alors, tâchons de progresser : pourquoi, puisqu'un tableau et un pointeur sont la même chose, faire le distingo ? Si vous le faites, c'est qu'il y a une raison. Au-dessus, tu dis "c'est plus propre". Comparé aux risques, pas évidents au premier abord, que tu décris par la suite, ça semble vraiment minime comme avantage. Alors quoi ? Quelle est la bonne manière de considérer les choses (je suis sûr que vous avez raison, hein grin), pour trouver ça avantageux, parce que pour le moment ça me parle pas trop cette distinction très formelle.

14

Un tableau est un pointeur ne sont pas la même chose, sauf dans le cas des paramètres de fonction (cf. http://c-faq.com/aryptr/aryptr2.html )
Du point de vue sémantique, un tableau est une collection d'objets de même type, d'adresse fixe et de taille fixe. Un pointeur est une référence (une adresse) vers un objet (cf. http://c-faq.com/aryptr/practdiff.html )
Là où le C est problématique est que partout où on utilise un tableau( sauf exceptions mineures style sizeof + initialisation), à l'utilisation, on a une conversion implicite d'un tableau en pointeur vers son premier élément (cf. http://c-faq.com/aryptr/aryptrequiv.html ). En fait, c'est que l'arithmétique de pointeurs et l'indexage de tableau sont la même chose: a[b] = *(a+b) (et c'est la norme C qui l'impose !)

On aurait pu imaginer que le C n'autorise pas l'arithmétique sur les pointeurs, et qu'il faille écrire:
 int n = ....;
 int (*p)[n] = calloc (n, sizeof(int));
 for(int i = 0; i < n; i++)
   (*p)[i] = i;

au lieu de :
 int n = ....;
 int *p = calloc (n, sizeof(int));
 for(int i = 0; i < n; i++)
   p[i] = i;

car p n'est pas un pointeur sur un entier, mais un pointeur vers un tableau d'entier, et c'est l'arithmétique sur les pointeurs + l'équivalence ci dessus qui permet d'écrire ce code simplifié.

Certaines personnes pensent que ca serait plus propre parce que les débutants ne comprennent pas la différence entre les deux expressions suivantes :
 char *str = "toto";
 char str[] = "toto";


Le risque présenté (sizeof d'un paramètre d'entrée du service) est assez faible après tout (Je n'ai jamais utilisé l'opérateur sizeof sur un paramètre d'entrée d'un service !).
Mais d'un autre coté, on peut imaginer que si l'on écrit
 void f(const int tab[])

des gens soient tentés de le faire pour calculer la taille du tableau passé en paramètre (et comment leur expliquer que non sans rentrer dans les détails sordides du C!)

On voit bien au final que les tableaux sont des objets de seconde classe dans la norme C...
(Par exemple, on a le fameux exemple où pour passer x en paramètre à f sans warning, on s'arrache les cheveux :
 void f(const int n[3][3]) { ...}
 int x[3][3];
)

Bref, c'est aussi pour çà que j'ai ouvert la discussion !

15

PpHd (./10) :
Encore plus troublant:
void f(const char str[1024]) {
printf("Size=%d\n", (int) sizeof(str));
}
affiche encore Size=8 ! (cf. http://c-faq.com/aryptr/aryparmsize.html )
Alors ça c'est effectivement sacrément vicieux, je ne m'en souvenais plus...
(et pour moi c'est un défaut de conception flagrant)

Mais finalement, je me demande si je n'étais pas tombé dessus quand j'ai commencé le C, et si ça n'a pas influencé mon choix de ne pas utiliser de tableaux dans les paramètres des fonctions.
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

16

Folco (./14) :
pourquoi, puisqu'un tableau et un pointeur sont la même chose

Ah non, non, non trinon
j'ai passé 2 putains de jours sur un bug dû à cette différence!
PpHd (./15) :
En fait, c'est que l'arithmétique de pointeurs et l'indexage de tableau sont la même chose: a[b] = *(a+b) (et c'est la norme C qui l'impose !)


oui mais si a est déclaré comme tableau, ça marche SEULEMENT pour des nombres positifs! Enfin presque, je m'explique:

avec les déclarations:

int a[]
int *b=a;
int offset;

a+1 est défini, et donne le même code asm que b+1
MAIS
a-1 est TOTALEMENT indéfini et le compilateur peut générer ce qu'il veut, de nop à reboot en passant par system("format c: /y") grin

alors que b-1 fonctionne

a noter que dans tous les cas, a+offset et a-offset ne pose pas de problème et fonctionne comme b+offset et b-offset, car le compilo ne peut rien optimiser!

On a rencontré un compilateur embarqué qui générait de la merde (non destructive, mais non fonctionnelle grin) pour l'expression tableau-offset. Et on n'a pas pu dire que c'était un bug de compilo, parce que ce n'est pas défini par l'ISO C. Mon collègue en a eu la confirmation sur le newsgroup comp.lang.c grin

c'est la seule différence que je connaisse entre tableau et pointeur triso

17

Oui mais bon, ton code est bugué à la base tongue
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

18

19

Non mais quand on code proprement, on écrit des trucs pareils ? grin
Franchement, écrire un indice négatif pour un tableau, alors que justement on s'abstrait pour obtenir une représentation à peu près humaine des données qu'on manipule, c'est hacker l'abstraction qu'on se propose d'utiliser en y insérant sa manière de calculer les adresses au niveau binaire, alors que précisément, à la base, on désire s'en éloigner. Enfin je vois ça comme ça.

20

squalyl (./16) :
oui mais si a est déclaré comme tableau, ça marche SEULEMENT pour des nombres positifs! Enfin presque, je m'explique:

avec les déclarations:

int a[]
int *b=a;
int offset;

a+1 est défini, et donne le même code asm que b+1
MAIS
a-1 est TOTALEMENT indéfini et le compilateur peut générer ce qu'il veut, de nop à reboot en passant par system("format c: /y") grin

alors que b-1 fonctionne

a noter que dans tous les cas, a+offset et a-offset ne pose pas de problème et fonctionne comme b+offset et b-offset, car le compilo ne peut rien optimiser!

On a rencontré un compilateur embarqué qui générait de la merde (non destructive, mais non fonctionnelle grin) pour l'expression tableau-offset. Et on n'a pas pu dire que c'était un bug de compilo, parce que ce n'est pas défini par l'ISO C. Mon collègue en a eu la confirmation sur le newsgroup comp.lang.c grin

c'est la seule différence que je connaisse entre tableau et pointeur triso


Norme C, chapitre 6.5.2.1 :

The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))).

Donc, c'est vraiment 100% équivalent.

Norme C, chapitre 6.5.6.8

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

*(b-1) n'est pas censé fonctionner puisque que 'b' et 'b-1' n'appartienne pas au même tableau.

21

Folco (./19) :
Non mais quand on code proprement, on écrit des trucs pareils ? grin



GT D'accord avec Folco calin
avatar
Accrochez vous ca va être Cerebral !!

22

il faut vraiment que je procure une copie de l'ISO C grin

on a aussi des trucs genre

uchar buffer[256+5];
#define data (buffer+5)

..... .....

if(data[3]==.....) etc etc

grin

23

PpHd (./10) :
Encore plus troublant:
 void f(const char str[1024]) {
 printf("Size=%d\n", (int) sizeof(str));
}

affiche encore Size=8 ! (cf. http://c-faq.com/aryptr/aryparmsize.html )

Pour cette raison, autant je ne vois pas vraiment de différence entre const char str[] et const char *str, autant je considère const char str[TAILLE] à bannir dans les paramètres. Ça induit trop facilement le lecteur en erreur pour être permis.
avatar
Maintenant j'ai la flemme de garder une signature à jour sur ce site. Je n'ai même plus ma chaîne Exec sous la main.

24

Sinon, pour les string tu crée un typedef vers char* ou vers char[], comme ça t'as plus la confusion.
Et puis si le langage n'inclus pas de type string, tu auras beau respecter une super convention dans tes code, les libs n'auront pas la même alors alors bon... embarrassed