Вопрос по c, const, char, string-literals, c++ – Почему передача строкового литерала в аргумент char * только иногда приводит к ошибке компилятора?

6

Я работаю в программах на 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 ++ - нет ... или наоборот ...

@ Брайан - Вы имеете в виду, что они несовместимы с другим вариантом компилятора? Пожалуйста, опубликуйте точное сообщение об ошибке, которое вы видите. В любом случае, целью включения записываемых строк является генерация этих предупреждений; они помогают показать, где не используется строкаconst-безопасный. Правильным решением было бы оставить записываемые строки включенными до тех пор, пока использование кода в вашем коде не было изменено так, чтобы опция «записываемые строки» больше не выдавала предупреждения. bta
@matteo: конечно. У меня вопрос, почему компилятор ловит некоторые из них, но не все ... Brian Postow
Практическое правило заключается в том, что строковые литералы следует всегда обрабатывать как const char *. Отношение к ним как к символу * подвергает вас таким проблемам. Matteo Italia
Поведение кода, который вы добавили, совершенно логично. Да, это должно скомпилироваться и работать, пока вы не попытаетесь что-то изменить. Однако, насколько я понял, ваша главная проблема заключалась в том, что компилятор обнаружил некоторые ошибки и не смог обнаружить некоторые другие ошибки. Это в одном из "провальных". Теперь покажите нам успешно «обнаруженный». AnT

Ваш Ответ

6   ответов
8

char* преобразование, которое тихо падаетconst квалификация. (4,2 / 2):

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

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

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

Это утверждение кажется мне немного противоречивым, простите меня, я новичок, и я пытаюсь понять это. "char * foo = "bar"; создает указатель на неконстантный символ и указывает на постоянный символ. " SSH This
@Patatoswatter конечно нет. Но если я помещу код C через g ++, он будет работать, но он также будет работать, если я поставлю его через gcc. У меня есть некоторые файлы .c и некоторые файлы .cpp. они могут быть скомпилированы с разными компиляторами ... Brian Postow
@Брайан:char * foo = "bar"; создает указатель на неконстантный символ и указывает на постоянный символ.char foo[] = "bar"; создает четырехсимвольный массив символов char, инициализируется с помощью 'b', 'a', 'r', '\ 0'. Заявления делают совершенно разные вещи. David Thornley
Я не совсем уверен, что означает эта цитата. Когда я это делаю, я получаю ошибки компилятора: char * foo = "bar"; но не когда я делаю char foo [] = "bar"; Brian Postow
1

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

Именно так. Это одна из причин, почему GCC имеет-Wwrite-strings вариант, но не включается по умолчанию или-Wall (это приведет к большому количеству предупреждений о старом или неконстантно-правильном коде). bta
2

я просто хочу добавить, что 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;
}

 

[email protected]:~/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.
[email protected]:~/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*’
[email protected]:~/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*’
[email protected]:~/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*’

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

[email protected]:~/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*’
[email protected]:~/cpp/test$ ./lit_const_corr.x 
Test
Test
Segmentation fault

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

[email protected]:~/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*’
[email protected]:~/cpp/test$ ./lit_const_corr.x 
Test
Test
Test

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

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

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

char * foo = "bar";

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

char foo[]="bar";

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

1

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

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

Ну, проблема в том, что это был случай, когда яHAD НЕ исправил это, потому что не было никакой ошибки, говорящей мне, чтобы исправить кое-что. это просто "" передавалось в аргумент char *. Brian Postow
1

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

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

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

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

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

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 Опция компилятора - это та, которую вы хотите включить, чтобы предупредить вас о подобных проблемах.

@ Брайан, ты прав. Это последний раз, когда я пытаюсь прочитать сложную проблему и опубликовать решение во время телефонной конференции, обещаю. Я отредактировал свой ответ, чтобы более точно решить проблему. bta
Я точно знаю, что делает delim. Изначально это не мой код ... и отсутствующий код в findargdefault вызывает другую функцию (в ветке if, которая не берется в случае сбоя), и возвращение не обязательно возвращается к const, поэтому изменяя эти символы * для conts не очень хорошая идея ... Brian Postow
Я не отключил -Wwrite-strings. Я отключил-пишущие-укусы. р не нулевой указатель. p - указатель на пустую строку. q символ *, а не символ, поэтому q = "", а не '\ 0'. да, функция уязвима для нулевого ptr, но я думаю, что мы достаточно ясно наверху, что этого не происходит ... Хотя защититься от этого не помешает, но это НЕ то, что здесь происходит. .. Brian Postow
6

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

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

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

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

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

char *p = "A";

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

Моя проблема не в переменных, а в аргументах. Когда я объявляю char * foo = "bar", он действительно жалуется. Но когда я объявляю char foo [] = "bar", это не так. Но я думаю, что это не связанная проблема. Brian Postow
Смотрите дополнение к моему ответу на этот вопрос. Matteo Italia
@ Брайан: причина, по которойchar foo[] = "bar" не выдает ошибку, потому что компилятор обрабатывает строку как инициализатор массива. Вместо того, чтобы объявлять указатель на постоянную строку (копировать адрес), он объявлял массив и инициализировал его значением «bar» (копирование данных). Данные вfoo должен быть изменчивым в этом случае. Это тонкий нюанс к двойственности массива / указателя. bta

Похожие вопросы