2010-10-06 4 views
21

Les fonctions de la bibliothèque standard C strtof et strtod avoir les signatures suivantes:Pourquoi le paramètre endptr strtof et strtod pointent-ils sur un pointeur char non-const?

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

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

  1. Un initiale, éventuellement vide, la séquence de espace
  2. Une "séquence de sujet" de caractères qui représentent une valeur à virgule flottante
  3. Une "séquence de fin" de caractères qui sont unrec reconnu (et qui n'affectent pas la conversion).

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

Je me demande: pourquoi est-endptr, puis, un pointeur vers un pointeur non constchar? *endptr n'est pas un pointeur dans une chaîne constchar (la chaîne d'entrée str)?

+0

C'est fondamentalement le même problème que 'strchr' et amis, sauf qu'ici nous avons un pointeur out-param plutôt qu'une valeur de retour. –

+0

@Steve: oui, mais c'est plus problématique que 'strchr' car vous ne pouvez pas passer l'adresse d'un pointeur qualifié' const' à ces fonctions sans un cast explicite. –

+3

Question intéressante. Fondamentalement, cela signifie que vous pouvez cacher une distribution de 'char const *' à 'char *' derrière les fonctions 'strtoX'. Bizarre. –

Répondre

10

La raison en est simplement l'utilisabilité. 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 d'appel 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 supprimer la qualification const en plusieurs étapes, chaque étape semblant parfaitement valide et correcte en elle-même. Steve Jessop a fourni un exemple dans les commentaires:

si vous pouvez convertir automatiquement char**-const char**, alors vous pourriez faire

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

Pour const-sécurité, l'une de ces lignes doit être illégale, et car les autres sont toutes les opérations tout à fait normal, il doit être cp = pp;

Une approche beaucoup mieux aurait été de définir ces fonctions à prendre void * à la place de char **. Les deux char ** et const char ** peuvent convertir automatiquement en void *. (Le texte endommagé était en fait une très mauvaise idée, non seulement il empêche tout contrôle de type, mais C interdit en fait les objets de type char * et const char *.) Sinon, ces fonctions auraient pu prendre un argument ptrdiff_t * ou size_t * dans lequel stocker le offset de la fin, plutôt que d'un pointeur. C'est souvent plus utile quand même. Si vous aimez cette dernière approche, n'hésitez pas à écrire un tel wrapper autour des fonctions de la bibliothèque standard et appelez votre wrapper, afin de garder le reste de votre code const -clean et sans cast.

+2

"Les deux' char ** 'et' const char ** 'peut convertir automatiquement en vide *." Je vois ce que vous dites, et l'utilisation de 'void *' permettrait à l'utilisateur de passer un 'const char **' sans une distribution. Mais cela leur permettrait aussi de passer dans tout un univers de mauvaises choses sans un casting. Compte tenu des limites de C, je pense qu'il vaut mieux préserver la sécurité de base, même le coût de la perte de sécurité. –

+0

Ce n'est pas simplement la convivialité, ces fonctions stockent le résultat dans ce pointeur, et vous ne pouvez pas écrire dans une mémoire constante. –

+0

@Vlad: Vous confondez 'const char **' avec 'char * const *'. –

4

Facilité d'utilisation. L'argument str est marqué const car l'argument d'entrée ne sera pas modifié. Si endptr étaient const, alors cela demanderait à l'appelant de ne pas modifier les données référencées à partir de endptr en sortie, mais souvent l'appelant veut faire exactement cela. Par exemple, je veux null-mettre fin à une chaîne après avoir obtenu le flotteur hors de lui:

float StrToFAndTerminate(char *Text) { 
    float Num; 

    Num = strtof(Text, &Text); 
    *Text = '\0'; 
    return Num; 
} 

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

Idéalement, endptr devrait être de constance correspondant à la constance d'entrée réelle de str, mais C ne fournit aucun moyen de l'indiquer par sa syntaxe. (Anders Hejlsberg talks about this lors de la description pourquoi const a été omis de C#.)

+0

Le même effet pourrait facilement être obtenu avec 'Text [FloatEnd-Text] = '\ 0'' donc pour moi ce n'est pas vraiment une bonne excuse. La méthode que vous indiquez produira UB si 'Text' serait déclaré avec' const' et vous ne pourriez pas facilement lire cela à partir de l'endroit où vous faites l'affectation. –

+0

@Jens: J'ai mis à jour le code pour mieux refléter la logique. Et je suis d'accord sur le problème de comportement indéfini. La question est de savoir si le remède est pire que la maladie ... –

+0

"pourrait facilement être atteint" - mais ce qui est l'optimisation prématurée à nous (préférant le pointeur directement, au lieu de compter sur le compilateur pour trier cette expression, et/ou le matériel à être si vite nous ne nous soucions pas), n'était pas l'optimisation prématurée au comité de normes C89 quand ils ont spécifié 'strtod'. Aussi, c'est un idiome honteux d'avoir à apprendre, donc on se demande juste si c'est plus ou moins honteux que const-incorrect ;-) –

Questions connexes