Почему компилятор не обнаруживает и не выдает ошибки при попытке изменить строковые литералы char *? - PullRequest
4 голосов
/ 24 января 2012

Предположим, что есть два следующих кода:

char *c = "hello world";
c[1] = 'y';

Приведенный выше не работает.

char c[] = "hello world";
c[1] = 'y';

Этот делает.

Что касается первого, я понимаю, что строка "hello world" может храниться в разделе только для чтения и, следовательно, не может быть изменена. Второй, однако, создает массив символов в стеке и, следовательно, может быть изменен.

У меня такой вопрос: почему компиляторы не обнаруживают первый тип ошибок? Почему это не часть стандарта C? Есть ли какая-то особая причина для этого?

Ответы [ 4 ]

4 голосов
/ 24 января 2012
Компиляторы

C не обязаны обнаруживать первую ошибку, поскольку строковые литералы C не являются const.

Ссылаясь на черновик N1256 стандарта C99 :

6.4.5, абзац 5:

На этапе 7 перевода байт или код нулевого значения добавляются к каждой многобайтовой последовательности символов, которая является результатом строкового литерала или литералов.Последовательность многобайтовых символов затем используется для инициализации массива статической длительности хранения и длины, достаточной только для того, чтобы содержать последовательность.Для строковых литералов символов элементы массива имеют тип char и инициализируются отдельными байтами многобайтовой последовательности символов;[...]

Пункт 6:

Не определено, различаются ли эти массивы при условии, что их элементы имеют соответствующие значения.Если программа пытается изменить такой массив, поведение не определено.

(C11 не меняет этого.)

Таким образом, строковый литерал "hello, world" имеет тип char[13] ( not const char[13]), который в большинстве случаев преобразуется в char*.

Попытка изменить объект const имеет неопределенное поведение, и большинство кода, который пытается это сделатьтак должен быть диагностирован компилятором (вы можете обойти это, например, с помощью приведения).Попытка изменить строковый литерал также ведет к неопределенному поведению, но не потому, что это const (это не так);это потому, что стандарт специально говорит, что поведение не определено.

Например, эта программа строго соответствует:

#include <stdio.h>

void print_string(char *s) {
    printf("%s\n", s);
}

int main(void) {
    print_string("Hello, world");
    return 0;
}

Если строковые литералы были const, то передача "Hello, world" вфункция, которая принимает (не const) char*, потребует диагностики.Программа действительна, но она будет демонстрировать неопределенное поведение, если print_string() попытается изменить строку, на которую указывает s.

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

gcc's -Wwrite-strings делаетзаставляет его обрабатывать строковые литералы как const, но делает gcc несовместимым компилятором, так как он не выдает диагностическое сообщение:

const char (*p)[6] = &"hello";

("hello" имеет тип char[6],поэтому &"hello" имеет тип char (*)[6], что несовместимо с объявленным типом p. С -Wwrite-strings, &"hello" рассматривается как тип const char (*)[6].) Предположительно, поэтому ни -Wallкроме того, -Wextra включает -Wwrite-strings.

С другой стороны, код, который вызывает предупреждение с -Wwrite-strings, вероятно, следует исправить в любом случае.Это неплохая идея написать свой код на C, чтобы он компилировался без диагностики как с -Wwrite-strings.

, так и без него (обратите внимание, что строковые литералы C ++ равны const, потому что когда Бьярн Страуструпразрабатывал C ++, он не был так обеспокоен строгой совместимостью со старым кодом C.)

3 голосов
/ 24 января 2012

Компиляторы могут обнаружить первую "ошибку".

В современных версиях gcc, если вы используете -Wwrite-strings, вы получите сообщение о том, что вы не можете назначить от const char* до char*. Это предупреждение включено по умолчанию для кода C ++.

Вот в чем проблема - первое присваивание, а не бит c[1] = 'y'. Конечно, законно взять char*, разыменовать его и присвоить разыменованному адресу.

Цитата из man 1 gcc:

When compiling C, give string constants the type "const char[length]" so that
copying the address of one into a non-"const" "char *" pointer will get a warning.
These warnings will help you find at compile time code that can try to write into a
string constant, but only if you have been very careful about using "const" in
declarations and prototypes. Otherwise, it will just be a nuisance. This is why we
did not make -Wall request these warnings.

Итак, в основном, потому что большинство программистов не писали код с правильной константой в первые дни C, это не стандартное поведение для gcc. Но это для g ++.

2 голосов
/ 24 января 2012

-Wwrite-strings, кажется, делает то, что вы хотите. Мог бы поклясться, что это было частью -Wall.

% cat chars.c 
#include <stdio.h>

int main()
{
  char *c = "hello world";
  c[1] = 'y';
  return 0;
}
% gcc -Wall -o chars chars.c          
% gcc -Wwrite-strings -o chars chars.c
chars.c: In function ‘main’:
chars.c:5: warning: initialization discards qualifiers from pointer target type

Из справочных страниц:

При компиляции C присвойте строковым константам тип "const char [length]", чтобы при копировании адреса единицы в не-"const" "char *" указатель получало предупреждение. Эти предупреждения помогут вам найти во время компиляции код, который может попытаться записать в строковую константу, но только если вы очень внимательно относились к использованию «const» в объявлениях и прототипах. В противном случае это будет просто неприятность. Вот почему мы не сделали - все запросят эти предупреждения.

При компиляции C ++ предупреждайте об устаревшем преобразовании строковых литералов в "char *". Это предупреждение по умолчанию включено для программ на C ++.

Обратите внимание, что "включено по умолчанию для C ++", возможно, поэтому я (и другие) думаю, что -Wall охватывает это. Также обратите внимание на объяснение, почему оно не является частью -Wall.

Что касается стандарта, C99 , 6.4.5, пункт 6 (стр. 63 в связанном PDF) гласит:

Не определено, различаются ли эти массивы при условии, что их элементы имеют соответствующие значения. Если программа пытается изменить такой массив, поведение недеформированные определенно.

0 голосов
/ 24 января 2012

char* c = strdup("..."); сделает c[1] разумным. ( Удалено разглагольствование на C ) Хотя интеллектуальный компилятор может / действительно предупреждает об этом, C традиционно находится рядом с машиной, без проверки (bounds / format / ...) и других подобных «ненужных» накладных расходов.

lint - это инструмент для обнаружения таких ошибок: что const char* было присвоено char*. Это также пометило бы char c = c[30]; (больше не зависит от типа, но также вызывает ошибку.) Как было бы хорошо объявить c как const char*. C является более старым языком с традицией снисхождения и работает на многих платформах.

...