Pourquoi le paramètre endptr de strtof et strtod est-il un pointeur sur un pointeur de caractère non-const?

Les fonctions standard de la bibliothèque C strtof et strtod ont les signatures suivantes:

 float strtof(const char *str, char **endptr); double strtod(const char *str, char **endptr); 

Ils décomposent chacun la chaîne d’entrée, str , en trois parties:

  1. Une première séquence d’espaces, éventuellement vide
  2. Une “séquence de sujet” de caractères représentant une valeur à virgule flottante
  3. Une “séquence de fin” de caractères non reconnus (et qui n’affectent pas la conversion).

Si endptr n’est pas NULL , alors *endptr est défini sur un pointeur sur le caractère qui suit immédiatement le dernier caractère faisant partie de la conversion (en d’autres termes, le début de la séquence de fin).

Je me demande: pourquoi endptr est- endptr alors un pointeur sur un pointeur non const ? *endptr n’est-il pas un pointeur dans une chaîne de caractère const (la chaîne d’entrée str )?

La raison est simplement la facilité d’utilisation. char * peut convertir automatiquement en const char * , mais char ** ne peut pas convertir automatiquement en const char ** , et le type réel du pointeur (dont l’adresse est passée) utilisé par la fonction appelante est beaucoup plus susceptible d’être char * que const char * . La raison pour laquelle cette conversion automatique n’est pas possible est qu’il existe un moyen non évident de l’utiliser pour supprimer la qualification de const en plusieurs étapes, chaque étape semblant parfaitement valide et correcte en soi. Steve Jessop a fourni un exemple dans les commentaires:

si vous pouviez convertir automatiquement char** en const char** , alors vous pourriez le faire

 char *p; char **pp = &p; const char** cp = pp; *cp = (const char*) "hello"; *p = 'j';. 

Pour la sécurité constante, l’une de ces lignes doit être illégale et, comme les autres sont des opérations parfaitement normales, cp = pp;

Une meilleure approche aurait été de définir ces fonctions pour prendre void * à la place de char ** . Les caractères char ** et const char ** peuvent tous deux être convertis automatiquement en void * . (Le texte endommagé était en fait une très mauvaise idée; non seulement il empêche toute vérification de type, mais C interdit en fait les objects de type char * et const char * à alias.) Sinon, ces fonctions auraient pu prendre ptrdiff_t * ou size_t * argument dans lequel stocker le décalage de la fin, plutôt qu’un pointeur sur celle-ci. C’est souvent plus utile de toute façon.

Si vous aimez cette dernière approche, n’hésitez pas à écrire un tel wrapper autour des fonctions de bibliothèque standard et à appeler votre wrapper, de manière à garder le rest de votre code propre et sans casting.

Facilité d’utilisation. L’argument str est marqué comme const car l’argument d’entrée ne sera pas modifié. Si endptr était const , cela indiquerait à l’ appelant qu’il ne devrait pas modifier les données référencées depuis endptr en sortie, mais c’est souvent ce que l’appelant souhaite faire. Par exemple, je souhaiterais peut-être mettre fin à une chaîne après l’avoir extraite du flottant:

 float StrToFAndTerminate(char *Text) { float Num; Num = strtof(Text, &Text); *Text = '\0'; return Num; } 

Chose parfaitement raisonnable à vouloir faire, dans certaines circonstances. Ne fonctionne pas si endptr est de type const char ** .

Idéalement, endptr devrait être endptr entrée réelle de str , mais C ne fournit aucun moyen de l’indiquer par le biais de sa syntaxe. ( Anders Hejlsberg parle de cela en décrivant pourquoi const été laissé en C #.)