Являются ли 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 *
и тем самым вызвать небезопасное изменение типа.) Разве это не так опасно?
Ответы
Ответ 1
Я бы предположил, что, поскольку альтернатива была хуже. Предположим, что прототип был изменен, чтобы добавить 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 **
. См. С++ FAQ Lite для подробного объяснения причин. Это технически говорит о С++, но аргументы одинаково справедливы для C. В C/С++ вам разрешено неявно преобразовывать из "указателя в тип" в "указатель на const
type" на самом высоком уровне: преобразование, которое вы можете выполнить, от char **
до char * const *
или эквивалентно от "указателя на (указатель на char
)" на "указатель на (const
указатель на char
)".
Так как я предполагаю, что синтаксический анализ непостоянной строки гораздо более вероятен, чем анализ постоянной строки, я бы сказал, что const
-корректность для маловероятного случая предпочтительнее сделать обычный случай ошибкой компилятора.
Ответ 2
Да, и другие функции имеют одинаковую проблему с "конт-отмыванием" (например, strchr, strstr, все эти партии).
Именно по этой причине С++ добавляет перегрузки (21.4: 4): подпись функции strchr(const char*, int)
заменяется двумя объявлениями:
const char* strchr(const char* s, int c);
char* strchr( char* s, int c);
Но, конечно, в C вы не можете иметь как const-правильные версии с тем же именем, поэтому получаете компрометацию const.
В С++ не упоминаются аналогичные перегрузки для strtol и strtod, и в действительности мой компилятор (GCC) их не имеет. Я не знаю, почему нет: тот факт, что вы не можете неявно отбрасывать char**
в const char**
(вместе с отсутствием перегрузки), объясняет это для C, но я не совсем понимаю, что было бы неправильно с Перегрузка С++:
long strtol(const char*, const char**, int);
Ответ 3
"const char *" для первого аргумента означает, что strtol()
не будет изменять строку.
Что вы делаете с возвращаемым указателем - это ваш бизнес.
Да, это можно рассматривать как нарушение безопасности типа; С++, вероятно, будет делать что-то по-другому (хотя, насколько я могу судить, ISO/IEC 14882: 1998 определяет <cstdlib>
с той же сигнатурой, что и в C).
Ответ 4
У меня есть компилятор, который предоставляет при компиляции в режиме С++:
extern "C" {
long int strtol(const char *nptr, const char **endptr, int base);
long int strtol(char *nptr, char **endptr, int base);
}
Очевидно, что оба они разрешают один и тот же символ времени привязки.
EDIT: в соответствии со стандартом С++ этот заголовок не должен компилироваться. Я предполагаю, что компилятор просто не проверял это. Определения действительно отображались как это в файлах заголовков системы.