2010-10-06 2 views
21

Стандартные функции библиотеки C strtof и strtod имеют следующие подписи:Почему параметр endptr для strtof и strtod является указателем на указатель не const const?

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

Каждый из них разлагаться входную строку, str, на три части:

  1. Начальная, возможно, пустой, последовательность whitespace
  2. «последовательность объектов» символов, которые представляют значение с плавающей запятой
  3. «Конечная последовательность» символов, которые являются неотрежимыми (и которые не влияют на преобразование).

Если endptr не NULL, то *endptr устанавливается на указатель на символ непосредственно после последнего символа, который был частью преобразования (другими словами, начало последовательности ведомой).

Я задаюсь вопросом: почему endptr, то указатель на не- указатель constchar? Не является ли *endptr указателем на строку constchar (строка ввода str)?

+0

Это в основном та же проблема, что и 'strchr' и друзей, за исключением того, что здесь у нас есть указатель out-param, а не возвращаемое значение. –

+0

@Steve: да, но это более проблематично, чем 'strchr', потому что вы не можете передать адрес указателя' const'-квалифицированного указателя на эти функции без явного приведения. –

+3

Интересный вопрос. В основном это означает, что вы можете скрыть приведение из 'char const *' в 'char *' за функциями 'strtoX'. Weird. –

ответ

10

Причина проста в использовании. char * может автоматически конвертировать в const char *, но char ** не может автоматически конвертировать в const char **, а фактический тип указателя (адрес которого передается), используемого вызывающей функцией, скорее всего будет char *, чем const char *. Причина, по которой это автоматическое преобразование невозможна, заключается в том, что существует неочевидный способ, которым он может быть использован для удаления квалификации const с помощью нескольких шагов, где каждый шаг выглядит совершенно корректным и само по себе. Steve Джессоп предоставил пример в комментариях:

, если вы можете автоматически конвертировать char** в const char**, то вы могли бы сделать

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

Для сопзЬ-безопасности, одна из этих линий должно быть незаконным, и так как другие все совершенно нормальные операции, он должен быть cp = pp;

гораздо лучше было бы определить эти функции взять void * вместо char **. Оба char ** и const char ** могут автоматически конвертировать в void *. (Исправленный текст на самом деле был очень плохой идеей, он не только предотвращает проверку типа, но и на самом деле запрещает объекты типа char * и const char * на псевдоним.) В качестве альтернативы эти функции могли бы принять аргумент или size_t *, храните смещение конца, а не указатель на него. Это часто более полезно.

Если вам нравится последний подход, не стесняйтесь писать такую ​​обертку вокруг стандартных функций библиотеки и вызывать свою обертку, чтобы сохранить остальную часть вашего кода const -clean и cast-free.

+2

«Оба символа' char ** 'и' const char ** 'может автоматически преобразовать в void *." Я понимаю, что вы говорите, и использование 'void *' позволило бы пользователю пройти в 'const char **' без трансляции. Но это также позволило бы им пройти во всей вселенной неправильных вещей без броска. Учитывая ограничения C, я считаю, что лучше сохранить базовую безопасность типа, даже стоимость потери постоянной безопасности. –

+0

Это не просто юзабилити, эти функции сохраняют результат в этом указателе, и вы не можете писать в постоянную память. –

+0

@ Vlad: Вы вводите в заблуждение 'const char **' с 'char * const *'. –

4

Удобство. Аргумент str помечен как const, потому что входной аргумент не будет изменен. Если endptr были const, то это должно было дать указание вызывающему , что он не должен изменять данные, на которые ссылается от endptr на выходе, но часто вызывающий абонент хочет сделать именно это. Например, я могу хотеть обнулить-завершить строку после получения поплавка из него:

float StrToFAndTerminate(char *Text) { 
    float Num; 

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

Совершенно разумно, что хочет сделать, в некоторых обстоятельствах. Не работает, если endptr имеет тип const char **.

В идеале endptr должен иметь константу, соответствующую фактической входной константе str, но C не дает возможности указывать это через свой синтаксис. (Anders Hejlsberg talks about this при описании того, почему const был исключен из C#.)

+0

Тот же эффект может быть легко достигнут с помощью «Text [FloatEnd-Text] = '\ 0'', поэтому для меня это не очень хорошее оправдание. Метод, который вы укажете, будет выдавать UB, если 'Text' будет объявлен с' const', и вы не могли бы легко прочитать это из того места, где вы выполняете задание. –

+0

@Jens: Я обновил код, чтобы лучше отразить обоснование. И я согласен с проблемой неопределенного поведения. Вопрос в том, является ли лечение хуже, чем болезнь ... –

+0

«можно было бы легко достичь» - но что досрочная оптимизация для нас (предпочитая указатель напрямую, вместо того, чтобы полагаться на компилятор, чтобы разобраться в этом выражении и/или аппаратное обеспечение настолько быстро, что нам все равно), не была преждевременной оптимизацией в комитете по стандартам C89, когда они указали 'strtod'. Кроме того, это позорная идиома, которую нужно изучать, поэтому нам просто осталось интересно, более или менее постыдно, чем const-некорректность ;-) –