简体   繁体   English

为什么有时将字符串文字传递给char *参数只是编译器错误?

[英]Why is passing a string literal into a char* argument only sometimes a compiler error?

I'm working in a C, and C++ program. 我正在使用C和C ++程序。 We used to be compiling without the make-strings-writable option. 我们曾经在没有make-strings-writable选项的情况下进行编译。 But that was getting a bunch of warnings, so I turned it off. 但是这得到了一堆警告,所以我把它关掉了。

Then I got a whole bunch of errors of the form "Cannot convert const char* to char* in argmuent 3 of function foo". 然后我得到了一堆形式的错误“无法将const char *转换为函数foo的argmuent 3中的char *”。 So, I went through and made a whole lot of changes to fix those. 所以,我经历了很多修改来解决这些问题。

However, today, the program CRASHED because the literal "" was getting passed into a function that was expecting a char*, and was setting the 0th character to 0. It wasn't doing anything bad, just trying to edit a constant, and crashing. 然而,今天,程序崩溃,因为文字“”被传递到一个期待char *的函数,并将第0个字符设置为0.它没有做任何坏事,只是试图编辑一个常量,并且崩溃。

My question is, why wasn't that a compiler error? 我的问题是,为什么不是编译器错误?

In case it matters, this was on a mac compiled with gcc-4.0. 如果它很重要,这是在使用gcc-4.0编译的mac上。

EDIT: added code: 编辑:添加代码:

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

where: 哪里:

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

and

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
}

This compiled and ran until it tried to set *q to 0... 这编译并运行,直到它试图将* q设置为0 ...

EDIT 2: 编辑2:

Most people seem to be missing the point of my question. 大多数人似乎都忽略了我的问题。 I know why char foo[] = "bar" works. 我知道为什么char foo [] =“bar”有效。 I know why char * foo = "bar"; 我知道为什么char * foo =“bar”; doesn't work. 不起作用。

My question is mostly with respect to passing parameters. 我的问题主要是关于传递参数。 One thing that occures to me is "Is it possible that this is a C vs C++ issue?" 有一件事发生在我身上:“这可能是C与C ++的问题吗?” because I have some .c files and some .cpp files, and it's quite possible that C allows it, but C++ doesn't... or vice versa... 因为我有一些.c文件和一些.cpp文件,C很可能允许它,但是C ++没有......反之亦然......

The standard specifies a special rule allowing the literal-to- char* conversion which quietly drops const qualification. 该标准规定了一个特殊的规则,允许文字到char*转换悄然降低const限定。 (4.2/2): (4.2 / 2):

A string literal (2.13.4) that is not a wide string literal can be converted to an rvalue of type “pointer to char”; 不是宽字符串文字的字符串文字(2.13.4)可以转换为“指向字符的指针”的右值; a wide string literal can be converted to an rvalue of type “pointer to wchar_t”. 可以将宽字符串文字转换为“指向wchar_t的指针”类型的右值。 In either case, the result is a pointer to the first element of the array. 在任何一种情况下,结果都是指向数组第一个元素的指针。 This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue. 仅当存在明确的适当指针目标类型时才考虑此转换,而不是在通常需要从左值转换为右值时。 [Note: this conversion is deprecated. [注意:此转换已弃用。 See Annex D. ] 见附件D.]

The C++0x standard takes that deprecation further… this nonsense rule is removed entirely from the upcoming standard. C ++ 0x标准进一步采用了这种弃用...这个无意义的规则完全从即将推出的标准中删除。

The const char* to char* error must be a result of converting a literal to a const char* first. const char* to char*错误必须是首先将文字转换为const char*

Using a string literal to initialize a char * pointer in C++ is a deprecated feature, but nevertheless it is legal. 使用字符串文字初始化C ++中的char *指针是一个不推荐使用的功能,但它是合法的。 It is not an error. 这不是错误。 It is your responsibility to make sure that no modification attempts are made through such a pointer. 您有责任确保不通过此类指针进行任何修改尝试。

In other words, you must be misunderstanding something about the compilation errors you got earlier. 换句话说,你必须误解你之前得到的编译错误。 I don't think you ever got any errors for such an initialization/assignment. 对于这样的初始化/分配,我认为你没有任何错误。 The "Cannot convert const char* to char*" errors you mention in your question must have been produced by something else. 您在问题中提到的“无法将const char *转换为char *”错误必须由其他内容生成。

Note, that the fact that you can initialize char * pointer with a string literal does not mean that you can use any arbitrary const char * value to initialize a char * pointer. 请注意,您可以使用字符串文字初始化char *指针并不意味着您可以使用任意const char *值来初始化char *指针。 This code 这段代码

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

will produce an error, while this 会产生错误,而这个

char *p = "A";

will not. 将不会。 The aforementioned deprecated feature applies to string literals only, not for all const char * pointers. 上述不推荐使用的功能仅适用于字符串文字,不适用于所有const char *指针。

I agree completely with the other answers, I just want to add that g++ (at least version 4.4) actually catches these deprecated conversions as warnings at any warning level (if previous versions do not do this by default, probably you have to raise the warning level): 我完全同意其他答案,我只想补充一点,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*’

Moreover, there's some interesting thing going on under the hood: if I compile it without optimization, it "just does what the code says", so it crashes, since it tries to write to a read-only memory location: 此外,引擎盖下还有一些有趣的事情:如果我在没有优化的情况下编译它,它“只是执行代码所说的”,因此它会崩溃,因为它会尝试写入只读内存位置:

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

but , if you turn on the optimizer, there's no crash: 但是 ,如果你打开优化器,就没有崩溃:

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

I suppose that this is due to some magic optimization trick, but I don't understand why is it applied; 我想这是由于一些神奇的优化技巧,但我不明白它为什么适用; any idea? 任何的想法?


Addendum 附录

When I declare a char* foo = "bar" it actualy complains. 当我宣布一个char * foo =“bar”时,它实际上会抱怨。 But when I declare char foo[] = "bar" it doesn't 但是当我声明char foo [] =“bar”时它没有

Hey, be careful not to confuse the two things: with 嘿,小心不要混淆这两件事:

char * foo = "bar";

you are declaring a pointer to char , and you assign to it the address of the literal "bar", which is actually stored in some read-only memory location (usually it's a part of the executable that is mapped in memory). 您正在声明一个指向char的指针 ,并为其分配文字“bar”的地址,该地址实际存储在某个只读内存位置(通常它是映射到内存中的可执行文件的一部分)。 Instead, with 相反,用

char foo[]="bar";

you are declaring and allocating RW memory (on the stack or somewhere else, depending from the context) for an array of chars , which is initialized with the "bar" value, but it is not related to the string table at all, and it's perfectly legit to change that string. 你正在声明和分配RW内存(在堆栈或其他地方,取决于上下文)为一个字符数组,用“bar”值初始化,但它根本不与字符串表相关,它是完全合法地改变那个字符串。

It really depends how you "went through and made a whole lot of changes to fix those." 这真的取决于你如何“经历并做出了很多修改来解决这些问题。”

If you just downcast the string literal to a char* then you're telling the compiler not to catch that error. 如果你只是将字符串文字向下转换为char*那么你告诉编译器不要捕获该错误。 You need to make a copy if you're going to modify it. 如果要修改它,则需要制作副本。 Otherwise declare your function interfaces to take a const so that the compiler can check these for you. 否则声明你的函数接口采用const以便编译器可以为你检查这些。

To answer the question why this conversion is legal (although deprecated). 回答这个转换为何合法的问题(尽管已弃用)。 Well there was a time, when there was no const keyword in the C language and people managed to produce a bit of code during that time. 好吧有一段时间,当C语言中没有const关键字时,人们在那段时间内设法生成了一些代码。 The designers of C++ must have figured out, that it wasn't a good idea to upset so many people, by breaking their code. C ++的设计者必须想通过打破他们的代码来打扰这么多人并不是一个好主意。

Since the stripCRLF function modifies a string in place but doesn't do anything with it or return any value, passing a string literal to it is essentially a no-op and should be considered a bug. 由于stripCRLF函数修改了一个字符串,但没有对它执行任何操作或返回任何值,因此将字符串文字传递给它本质上是一个无操作,应该被视为一个错误。 You can either solve this by having the function modify and return a copy of the string, or by setting stricter warning flags to help detect when this happens. 您可以通过修改函数并返回字符串的副本来解决此问题,也可以通过设置更严格的警告标志来帮助检测何时发生这种情况。

If you want gcc to alert you of things like this, turn on the -Wwrite-strings compiler option. 如果你想让gcc提醒你这样的事情,请打开-Wwrite-strings编译器选项。 This will force the compiler to warn you if a string constant is converted to a non-constant char* . 这将强制编译器在字符串常量转换为非常量char*发出警告。 Also possibly useful is the -Wcast-qual option; 同样可能有用的是-Wcast-qual选项; this should emit a warning whenever a pointer is cast in a way that removes a type qualifier (in your case, the const got removed). 每当指针以一种删除类型限定符的方式进行转换时,这应该发出警告(在你的情况下, const被删除)。 If you want these messages to be made more strongly, use -Werror to turn all warnings into errors. 如果您希望更强烈地使用这些消息,请使用-Werror将所有警告变为错误。

The other point of contention is the FindArgDefault function. 另一个争论点是FindArgDefault函数。 As provided, the function signature should more accurately use const char* instead of char* for the return and parameter types. 如前所述,函数签名应该更准确地使用const char*而不是char*作为返回和参数类型。 This should cause the compiler to complain when the return value is assigned to a char* (if the -Wcast-qual option is used). 这会导致编译器在将返回值分配给char*时进行投诉(如果使用了-Wcast-qual选项)。 Since you didn't post the complete function, this might not be a valid change to make. 由于您没有发布完整的功能,因此这可能不是一个有效的更改。 If either string is modified inside the function then the corresponding parameter must remain a char* , but in that event passing a string literal as the argument should generate a compiler warning (use -Wwrite-strings ). 如果在函数内部修改了任一字符串,则相应的参数必须保持为char* ,但在该事件中,将字符串文字作为参数传递应生成编译器警告(使用-Wwrite-strings )。

By the way, your stripCRLF function is vulnerable to problems when a NULL pointer is passed in. Also, did you mean to say if (delim == -1) , or should that be != ? 顺便说一下,当传入NULL指针时,你的stripCRLF函数很容易出现问题。另外,你的意思是说if (delim == -1) ,还是应该是!=

Edit: After seeing more information about the error messages the OP was getting, I removed parts of the original post that were off-topic and added some additional comments. 编辑:在看到有关OP正在获取的错误消息的更多信息后,我删除了原始帖子中偏离主题的部分内容并添加了一些其他注释。

Edit2: I tested the following simplified version of your program: Edit2:我测试了你的程序的以下简化版本:

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

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

When I compiled with gcc -Wall test.c -o test.o , I got zero compiler warnings or errors. 当我用gcc -Wall test.c -o test.o编译时,我得到零编译器警告或错误。

When I compiled with gcc -Wwrite-strings -Wall test.c -o test.o , I got 当我用gcc -Wwrite-strings -Wall test.c -o test.o编译时,我得到了

test.c: In function 'main': test.c:在函数'main'中:

test.c:10: warning: passing arg 1 of 'FindArgDefault' discards qualifiers from pointer target type test.c:10:警告:传递'FindArgDefault'的arg 1会丢弃指针目标类型的限定符

test.c:10: warning: passing arg 2 of 'FindArgDefault' discards qualifiers from pointer target type test.c:10:警告:传递'FindArgDefault'的arg 2会丢弃指针目标类型的限定符

I definitely think that the -Wwrite-strings compiler option is the one you want to enable to warn you about this sort of problem. 我绝对认为-Wwrite-strings编译器选项是你想要启用以警告你这类问题的选项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将字符串文字传递给采用&#39;char *&#39;的函数并得到编译器警告 - Passing string literal to function taking 'char *' and getting a compiler warning 将文字字符串作为const char *参数传递会导致代码分析器错误 - passing literal string as const char * parameter causes code analyzer error 通过参数将字符串文字传递为char * - Pass string literal as char* via argument 是否适合在函数参数中将字符串文字转换为char *? - Appropriate to cast string literal to char * in function argument? 将字符串文字传递给模板字符数组参数 - Passing a string literal to a template char array parameter 为什么我会收到编译器警告,将字符串文字转换为char *,这是不是很糟糕? - Why do I get a compiler warning for converting a string literal to a char*, is it bad? 用字符串参数调用构造函数传递char * 报错 - Calling constructor with string argument passing char * report error 将字符串文字作为类型参数传递给 class 模板 - Passing a string literal as a type argument to a class template 为什么编译器在给定一个const char *作为模板化参数的类型时选择bool而不是string_view? - why does compiler choose bool over string_view when given a const char * as the type of the templated argument? 为什么将字符串作为文件名而不是char *传递时出现错误? - Why do I get an error when passing a string as a filename, but not a char*?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM