Почему передача строкового литерала в аргумент char * только иногда приводит к ошибке компилятора? - PullRequest
7 голосов
/ 03 мая 2010

Я работаю в программах на C и C ++. Мы привыкли компилировать без опции make-strings-writable. Но это получало кучу предупреждений, поэтому я выключил его.

Затем я получил целую кучу ошибок вида «Невозможно преобразовать const char * в char * в аргументе 3 функции foo». Итак, я прошел и сделал много изменений, чтобы исправить их.

Однако сегодня программа CRASHED, потому что литерал "" передавался в функцию, которая ожидала символ *, и устанавливал 0-й символ в 0. Это не было ничего плохого, просто пытался редактировать постоянный и сбой.

Мой вопрос: почему это не ошибка компилятора?

В случае, если это имеет значение, это был Mac, скомпилированный с gcc-4.0.

РЕДАКТИРОВАТЬ: добавлен код:

char * host = FindArgDefault("EMailLinkHost", "");
stripCRLF(linkHost, '\n');

где:

char *FindArgDefault(char *argName, char *defVal) 
{// simplified
    char * val = defVal;
    return(val);
}

и

void stripCRLF(char *str, char delim)
{
    char *p, *q;

    for (p = q = str; *p; ++p) {
        if (*p == 0xd || *p == 0xa) {
            if (p[1] == (*p ^ 7)) ++p;
            if (delim == -1) *p = delim;
            }
        *q++ = *p;
        }
    *q = 0;  // DIES HERE
}

Это скомпилировано и работает, пока не попытается установить * q в 0 ...

РЕДАКТИРОВАТЬ 2:

Большинство людей, похоже, не понимают сути моего вопроса. Я знаю, почему char foo [] = "bar" работает. Я знаю, почему char * foo = "bar"; не работает

Мой вопрос в основном касается передачи параметров. Одна вещь, которая приходит мне в голову: «Возможно ли, что это проблема C против C ++?» потому что у меня есть некоторые файлы .c и некоторые файлы .cpp, и вполне возможно, что C это позволяет, а C ++ - нет ... или наоборот ...

Ответы [ 6 ]

8 голосов
/ 03 мая 2010

Стандарт определяет специальное правило, разрешающее преобразование литерала в char*, которое незаметно понижает квалификацию const. (4,2 / 2):

Строковый литерал (2.13.4), который не является строковым литералом, может быть преобразован в значение типа «указатель на символ»; широкий строковый литерал может быть преобразован в значение типа «указатель на wchar_t». В любом случае результатом является указатель на первый элемент массива. Это преобразование рассматривается только при наличии явного соответствующего целевого типа указателя, а не при общей необходимости преобразования из lvalue в rvalue. [Примечание: это преобразование устарело. См. Приложение D.]

В стандарте C ++ 0x эта амортизация еще более расширена ... это бессмысленное правило полностью исключено из будущего стандарта.

Ошибка от const char* до char* должна быть результатом преобразования литерала в const char* сначала.

6 голосов
/ 03 мая 2010

Использование строкового литерала для инициализации указателя char * в C ++ является устаревшей функцией, но, тем не менее, это допустимо. Это не ошибка. Вы несете ответственность за то, чтобы через такой указатель не предпринимались попытки изменения.

Другими словами, вы должны неправильно понимать что-то об ошибках компиляции, которые вы получили ранее. Я не думаю, что вы когда-либо получали какие-либо ошибки для такой инициализации / назначения. Ошибки "Cannot convert const char * to char *", о которых вы упоминаете в своем вопросе, должны быть вызваны чем-то другим.

Обратите внимание, что тот факт, что вы можете инициализировать указатель char * строковым литералом, не означает, что вы можете использовать любое произвольное значение const char * для инициализации указателя char *. Этот код

const char *pc = "A";
char *p = pc;

выдаст ошибку, тогда как этот

char *p = "A";

не будет. Вышеупомянутая устаревшая функция применяется только к строковым литералам, но не ко всем const char * указателям.

2 голосов
/ 04 мая 2010

Я полностью согласен с другими ответами, я просто хочу добавить, что g ++ (по крайней мере, версия 4.4) фактически отлавливает эти устаревшие преобразования как предупреждения на любом уровне предупреждений (если предыдущие версии не делают этого по умолчанию, возможно, вам придется поднять уровень предупреждения):

#include <iostream>

using namespace std;

void WithConst(const char * Str)
{
    cout<<Str<<endl;
}

void WithoutConst_NoEdit(char * Str)
{
    cout<<Str<<endl;
}

void WithoutConst_Edit(char * Str)
{
    *Str='a';
    cout<<Str<<endl;
}

int main()
{
    WithConst("Test");
    WithoutConst_NoEdit("Test");
    WithoutConst_Edit("Test");
    return 0;
}

matteo@teoubuntu:~/cpp/test$ g++ --version
g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
matteo@teoubuntu:~/cpp/test$ g++ -O3 lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’
matteo@teoubuntu:~/cpp/test$ g++ -O3 -Wall lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’
matteo@teoubuntu:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’

Более того, под капотом происходит кое-что интересное: если я скомпилирую его без оптимизации, он «просто делает то, что говорит код», поэтому он вылетает, так как он пытается записать в область памяти только для чтения:

matteo@teoubuntu:~/cpp/test$ g++ -Wall -Wextra -ansi -pedantic lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’
matteo@teoubuntu:~/cpp/test$ ./lit_const_corr.x 
Test
Test
Segmentation fault

но , если вы включите оптимизатор, сбой не произойдет:

matteo@teoubuntu:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’
matteo@teoubuntu:~/cpp/test$ ./lit_const_corr.x 
Test
Test
Test

Полагаю, это связано с неким волшебным приемом оптимизации, но я не понимаю, почему он применяется; есть идеи?

<ч />

Добавление

Когда я объявляю char * foo = "bar", он действительно жалуется. Но когда я объявляю char foo [] = "bar", это не

Эй, будьте осторожны, чтобы не перепутать две вещи: с

char * foo = "bar";

вы объявляете указатель на символ , и вы присваиваете ему адрес буквального «бара», который на самом деле хранится в некотором месте только для чтения (обычно это часть исполняемого файла) это отображается в памяти). Вместо этого с

char foo[]="bar";

вы объявляете и выделяете память RW (в стеке или где-то еще, в зависимости от контекста) для массива символов , который инициализируется значением "bar", но это не связано к таблице строк вообще, и вполне допустимо изменить эту строку.

1 голос
/ 04 мая 2010

Так как функция stripCRLF изменяет строку на месте, но ничего с ней не делает и не возвращает никакого значения, передача строкового литерала ему по сути является запретом и должна рассматриваться как ошибка. Вы можете решить эту проблему, изменив и вернув копию строки, или установив более строгие флаги предупреждения, чтобы помочь определить, когда это происходит.

Если вы хотите, чтобы gcc предупреждал вас о подобных вещах, включите опцию -Wwrite-strings compiler. Это заставит компилятор предупреждать вас, если строковая константа преобразуется в непостоянную char*. Также возможно полезным является параметр -Wcast-qual; это должно выдавать предупреждение всякий раз, когда указатель приведен таким образом, который удаляет спецификатор типа (в вашем случае, const был удален). Если вы хотите, чтобы эти сообщения делались более строго, используйте -Werror, чтобы превратить все предупреждения в ошибки.

Другим предметом спора является функция FindArgDefault. Как предусмотрено, сигнатура функции должна более точно использовать const char* вместо char* для типов возврата и параметров. Это должно привести к тому, что компилятор будет жаловаться, когда возвращаемое значение назначено char* (если используется опция -Wcast-qual). Поскольку вы не опубликовали полную функцию, это может быть недопустимым изменением. Если какая-либо строка изменяется внутри функции, то соответствующий параметр должен оставаться char*, но в этом случае передача строкового литерала в качестве аргумента должна сгенерировать предупреждение компилятора (используйте -Wwrite-strings).

Кстати, ваша функция stripCRLF уязвима для проблем, когда передается указатель NULL. Кроме того, вы хотели сказать if (delim == -1), или это должно быть !=?

Редактировать: После просмотра дополнительной информации о сообщениях об ошибках, которые получает ОП, я удалил части исходного поста, которые были не по теме, и добавил некоторые дополнительные комментарии.

Edit2: Я протестировал следующую упрощенную версию вашей программы:

char *FindArgDefault(char *argName, char *defVal) {
    char * val = defVal;
    return(val);
}

int main (void) {
    char * host = FindArgDefault("EMailLinkHost", "");
    return (int)(host);
}

Когда я компилировал с gcc -Wall test.c -o test.o, я получал ноль предупреждений или ошибок компилятора.

Когда я скомпилировал с gcc -Wwrite-strings -Wall test.c -o test.o, я получил

test.c: в функции 'main':

test.c: 10: предупреждение: передача аргумента 1 из FindArgDefault отбрасывает квалификаторы из целевого типа указателя

test.c: 10: предупреждение: передача аргумента 2 из FindArgDefault отбрасывает квалификаторы из целевого типа указателя

Я определенно думаю, что опцию компилятора -Wwrite-strings вы хотите включить, чтобы предупредить вас о подобных проблемах.

1 голос
/ 04 мая 2010

Чтобы ответить на вопрос, почему это преобразование является законным (хотя и не рекомендуется). Ну, было время, когда в языке C не было ключевого слова const, и людям удалось создать немного кода за это время. Разработчики C ++, должно быть, поняли, что не стоит расстраивать так много людей, ломая их код.

1 голос
/ 03 мая 2010

Это действительно зависит от того, как вы «прошли и внесли множество изменений, чтобы исправить их».

Если вы просто понижаете строковый литерал до char*, то вы говорите компилятору , а не , чтобы перехватить эту ошибку. Вам нужно сделать копию, если вы собираетесь ее изменить. В противном случае объявите ваши интерфейсы функций, чтобы они взяли const, чтобы компилятор мог проверить их для вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...