Strtol, strtod небезопасны?

Кажется, что strtol() и strtod() эффективно позволяют (и заставляют) отказаться от константности в строке:

#include <stdlib.h>
#include <stdio.h>

int main() {
  const char *foo = "Hello, world!";
  char *bar;
  strtol(foo, &bar, 10); // or strtod(foo, &bar);
  printf("%d\n", foo == bar); // prints "1"! they're equal
  *bar = 'X'; // segmentation fault
  return 0;
}

Выше я не выполнял никаких забросов сам. Тем не менее, strtol() в основном превратил мой const char * в char * для меня, без каких-либо предупреждений или чего-то еще. (На самом деле, это не позволит вам ввести bar как const char * и, таким образом, вызовет небезопасное изменение типа.) Разве это не опасно?


person user102008    schedule 14.06.2009    source источник


Ответы (4)


Я предполагаю, что это потому, что альтернатива была хуже. Предположим, что прототип был изменен, чтобы добавить const:

long int strtol(const char *nptr, const char **endptr, int base);

Теперь предположим, что мы хотим разобрать непостоянную строку:

char str[] = "12345xyz";  // non-const
char *endptr;
lont result = strtol(str, &endptr, 10);
*endptr = '_';
printf("%s\n", str);  // expected output: 12345_yz

Но что происходит, когда мы пытаемся скомпилировать этот код? Ошибка компилятора! Это довольно неинтуитивно, но вы не можете неявно преобразовать char ** в const char **. Подробнее см. C++ FAQ Lite. почему. Технически там говорится о C++, но аргументы в равной степени действительны и для C. В C/C++ вам разрешено неявно преобразовывать только «указатель на тип» в «указатель на const type» на самом высоком уровне: преобразование, которое вы можете выполнить, — это преобразование из char ** в char * const * или, что то же самое, из «указателя на (указатель на char)» в «указатель на (const указатель на char)».

Поскольку я предполагаю, что синтаксический анализ непостоянной строки гораздо более вероятен, чем синтаксический анализ константной строки, я бы постулировал, что const-некорректность для маловероятного случая предпочтительнее, чем превращение общего случая в ошибку компилятора.

person Adam Rosenfield    schedule 14.06.2009
comment
Но C++ не мешает вам перегружать функцию: вы можете использовать long int strtol(char *nptr, char **endptr, int base); и long int strtol(const char *nptr, const char **endptr, int base);: это исправит вашу ошибку компиляции. Действительно, стандарт делает это для других подобных функций, таких как strchr и strstr, - person Thanatos; 30.03.2011
comment
Вы можете обратиться к веб-сайту часто задаваемых вопросов C В чем разница между const char *p, char const *p и char * const p? и, в частности, почему я не могу передать char ** функции, которая ожидает const char ** ? вместо часто задаваемых вопросов по C++, хотя я не совсем уверен, что объяснения легко понять. - person Jonathan Leffler; 13.01.2016

Да, и другие функции имеют ту же проблему "отмывания констант" (например, strchr, strstr и все такое).

Именно по этой причине C++ добавляет перегрузки (21.4:4): сигнатура функции strchr(const char*, int) заменяется двумя объявлениями:

const char* strchr(const char* s, int c);
      char* strchr(      char* s, int c);

Но, конечно, в C у вас не может быть обеих const-корректных версий с одним и тем же именем, поэтому вы получаете компромисс с const-incorrect.

В C++ не упоминаются аналогичные перегрузки для strtol и strtod, да и в моем компиляторе (GCC) их нет. Я не знаю, почему нет: тот факт, что вы не можете неявно преобразовать char** в const char** (вместе с отсутствием перегрузки), объясняет это для C, но я не совсем понимаю, что было бы не так с перегрузкой C++:

long strtol(const char*, const char**, int);
person Steve Jessop    schedule 15.06.2009
comment
stlport предоставляет эту перегрузку (и некоторые другие). - person Alex Cohn; 03.03.2013

'const char *' для первого аргумента означает, что strtol() не будет изменять строку.

Что вы делаете с возвращенным указателем, это ваше дело.

Да, это можно рассматривать как нарушение безопасности типов; C++, вероятно, поступил бы по-другому (хотя, насколько я могу судить, ISO/IEC 14882:1998 определяет <cstdlib> с той же сигнатурой, что и в C).

person Jonathan Leffler    schedule 14.06.2009
comment
C++ определяет strtol (и все остальное в cstdlib) с той же сигнатурой, что и C, но не все в cstring и cwchar. - person Steve Jessop; 15.06.2009

У меня есть компилятор, который обеспечивает при компиляции в режиме С++:

extern "C" {
long int strtol(const char *nptr, const char **endptr, int base);
long int strtol(char *nptr, char **endptr, int base);
}

Очевидно, что они оба разрешаются в один и тот же символ времени компоновки.

РЕДАКТИРОВАТЬ: в соответствии со стандартом C++ этот заголовок не должен компилироваться. Я предполагаю, что компилятор просто не проверил это. Определения действительно появились в системных заголовочных файлах.

person Joshua    schedule 15.06.2009