Почему передача является строковым литералом в символ* аргумент только иногда ошибка компилятора?

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

Затем я добрался, целый набор ошибок формы "Не может преобразовать символ константы* для обугливания* в argmuent 3 функционального нечто". Так, я прошел и внес большое изменения для фиксации их.

Однако сегодня программа ОТКАЗАЛА, потому что литерал "" становился переданным в функцию, которая ожидала символ* и устанавливала 0th символ на 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:

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

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

7
задан Brian Postow 3 May 2010 в 21:48
поделиться

6 ответов

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

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

1
ответ дан 6 December 2019 в 09:58
поделиться

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

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

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

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

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

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: In function 'main':

test.c:10: warning: passing arg 1 of 'FindArgDefault' discards qualifiers from pointer target type

test.c:10: warning: passing arg 2 of 'FindArgDefault' discards qualifiers from pointer target type

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

1
ответ дан 6 December 2019 в 09:58
поделиться

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

1
ответ дан 6 December 2019 в 09:58
поделиться

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

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

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

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

вызовет ошибку, а этот

char *p = "A";

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

6
ответ дан 6 December 2019 в 09:58
поделиться

Я полностью согласен с другими ответами, я только хочу добавить, что 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, и присваиваете ему адрес литерала "bar", который на самом деле хранится в каком-то доступном только для чтения месте памяти (обычно это часть исполняемого файла, который отображается в памяти). Вместо этого, с помощью

char foo[]="bar";

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

2
ответ дан 6 December 2019 в 09:58
поделиться

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

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

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

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

8
ответ дан 6 December 2019 в 09:58
поделиться