为什么将字符串文字传递到 char* 参数有时只会出现编译器错误?
我正在从事 C 和 C++ 程序。我们曾经在没有 make-strings-writable 选项的情况下进行编译。但它收到了一堆警告,所以我把它关掉了。
然后我得到了一大堆“Cannot conversion const char* to char* in argmuent 3 of function foo”形式的错误。所以,我经历并做了很多改变来解决这些问题。
然而,今天,程序崩溃了,因为文字“”被传递到一个需要 char* 的函数中,并将第 0 个字符设置为 0。它没有做任何坏事,只是试图编辑一个常量,并且崩溃了。
我的问题是,为什么这不是编译器错误?
如果重要的话,这是在使用 gcc-4.0 编译的 mac 上进行的。
编辑:添加代码:
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
}
这编译并运行,直到它尝试将 *q 设置为 0...
编辑2:
大多数人似乎都忽略了我的问题的要点。我知道为什么 char foo[] = "bar" 有效。我知道为什么 char * foo = "bar";不起作用。
我的问题主要是关于传递参数。我想到的一件事是“这有可能是 C 与 C++ 的问题吗?”因为我有一些 .c 文件和一些 .cpp 文件,并且 C 很可能允许,但 C++ 不允许...或者反之亦然...
I'm working in a C, and C++ program. We used to be compiling without the make-strings-writable option. 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". 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.
My question is, why wasn't that a compiler error?
In case it matters, this was on a mac compiled with gcc-4.0.
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...
EDIT 2:
Most people seem to be missing the point of my question. I know why char foo[] = "bar" works. I know why 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?" 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...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
该标准指定了一条特殊规则,允许文字到 char* 的转换,从而悄悄地删除 const 限定。 (4.2/2):
C++0x 标准进一步取消了这一弃用……这条无意义的规则已从即将发布的标准中完全删除。
const char*
到char*
错误必须是首先将文字转换为const char*
的结果。The standard specifies a special rule allowing the literal-to-
char*
conversion which quietly dropsconst
qualification. (4.2/2):The C++0x standard takes that deprecation further… this nonsense rule is removed entirely from the upcoming standard.
The
const char*
tochar*
error must be a result of converting a literal to aconst char*
first.在 C++ 中使用字符串文字来初始化 char * 指针是一项已弃用的功能,但它仍然是合法的。这不是一个错误。您有责任确保不会通过此类指针进行任何修改尝试。
换句话说,您一定误解了之前遇到的编译错误。我认为您在此类初始化/分配中不会遇到任何错误。您在问题中提到的“无法将 const char* 转换为 char*”错误一定是由其他原因产生的。
请注意,您可以使用字符串文字初始化 char * 指针这一事实并不意味着您可以使用任意 const char * 值来初始化 char * 指针。这段代码
会产生错误,而这则
不会。上述已弃用的功能仅适用于字符串文字,不适用于所有 const char * 指针。
Using a string literal to initialize a
char *
pointer in C++ is a deprecated feature, but nevertheless it is legal. 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.
Note, that the fact that you can initialize
char *
pointer with a string literal does not mean that you can use any arbitraryconst char *
value to initialize achar *
pointer. This codewill produce an error, while this
will not. The aforementioned deprecated feature applies to string literals only, not for all
const char *
pointers.我完全同意其他答案,我只是想补充一点,g++(至少版本 4.4)实际上将这些已弃用的转换捕获为任何警告级别的警告(如果以前的版本默认情况下不这样做,可能您必须提出警告级别)
:
此外,在幕后发生了一些有趣的事情:如果我在没有优化的情况下编译它,它“只是按照代码所说的去做”,所以它崩溃了,因为它尝试写入只读内存位置:
< em>但是,如果你打开优化器,就不会崩溃:
我想这是由于一些神奇的优化技巧,但我不明白为什么要应用它;有什么想法吗?
附录
嘿,请小心不要混淆这两件事:
您声明了指向 char 的指针,并且为它分配了地址文字“bar”的,它实际上存储在某个只读内存位置(通常它是映射到内存中的可执行文件的一部分)。
相反,
您为字符数组声明和分配RW内存(在堆栈上或其他地方,具体取决于上下文),该数组是用“bar”值初始化的,但它不是与字符串表完全相关,并且更改该字符串是完全合法的。
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):
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:
but, if you turn on the optimizer, there's no crash:
I suppose that this is due to some magic optimization trick, but I don't understand why is it applied; any idea?
Addendum
Hey, be careful not to confuse the two things: with
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).
Instead, with
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.
这实际上取决于你如何“经历并做出大量改变来解决这些问题”。
如果您只是将字符串文字向下转换为
char*
,那么您就是在告诉编译器不捕获该错误。如果您要修改它,则需要制作一份副本。否则,声明您的函数接口采用 const,以便编译器可以为您检查这些。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. You need to make a copy if you're going to modify it. Otherwise declare your function interfaces to take aconst
so that the compiler can check these for you.回答为什么这种转换是合法的(尽管已弃用)的问题。曾经有一段时间,C 语言中没有 const 关键字,人们在那段时间设法编写了一些代码。 C++ 的设计者一定已经意识到,通过破坏代码来让这么多人感到不安并不是一个好主意。
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. The designers of C++ must have figured out, that it wasn't a good idea to upset so many people, by breaking their code.
由于
stripCRLF
函数会就地修改字符串,但不会对其执行任何操作或返回任何值,因此向其传递字符串文字本质上是无操作,应被视为错误。您可以通过让函数修改并返回字符串的副本来解决此问题,或者通过设置更严格的警告标志来帮助检测何时发生这种情况。如果您希望 gcc 提醒您此类情况,请打开
-Wwrite-strings
编译器选项。如果字符串常量转换为非常量char*
,这将强制编译器发出警告。-Wcast-qual
选项也可能有用;每当以删除类型限定符的方式强制转换指针时(在您的情况下,const 被删除),这应该发出警告。如果您希望这些消息更加强烈,请使用-Werror
将所有警告转为错误。另一个争论点是 FindArgDefault 函数。正如所提供的,函数签名应该更准确地使用
const char*
而不是char*
作为返回和参数类型。当返回值分配给char*
时(如果使用-Wcast-qual
选项),这会导致编译器发出警告。由于您没有发布完整的功能,因此这可能不是有效的更改。如果在函数内部修改任一字符串,则相应的参数必须保留为char*
,但在这种情况下,传递字符串文字作为参数应该会生成编译器警告(使用-Wwrite-strings )。
顺便说一句,当传入 NULL 指针时,您的
stripCRLF
函数很容易出现问题。另外,您的意思是if (delim == -1)
或应该是!=
吗?编辑:在看到有关OP收到的错误消息的更多信息后,我删除了原始帖子中与主题无关的部分,并添加了一些附加评论。
编辑2:我测试了您的程序的以下简化版本:
当我使用
gcc -Wall test.c -o test.o
编译时,我得到了零编译器警告或错误。当我用 gcc -Wwrite-strings -Wall test.c -o test.o 编译时,我得到了
我绝对认为
-Wwrite-strings
编译器选项是您想要启用的选项,以警告您此类问题。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. 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. This will force the compiler to warn you if a string constant is converted to a non-constantchar*
. Also possibly useful is the-Wcast-qual
option; this should emit a warning whenever a pointer is cast in a way that removes a type qualifier (in your case, theconst
got removed). If you want these messages to be made more strongly, use-Werror
to turn all warnings into errors.The other point of contention is the
FindArgDefault
function. As provided, the function signature should more accurately useconst char*
instead ofchar*
for the return and parameter types. This should cause the compiler to complain when the return value is assigned to achar*
(if the-Wcast-qual
option is used). 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 achar*
, but in that event passing a string literal as the argument should generate a compiler warning (use-Wwrite-strings
).By the way, your
stripCRLF
function is vulnerable to problems when a NULL pointer is passed in. Also, did you mean to sayif (delim == -1)
, or should that be!=
?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.
Edit2: I tested the following simplified version of your program:
When I compiled with
gcc -Wall test.c -o test.o
, I got zero compiler warnings or errors.When I compiled with
gcc -Wwrite-strings -Wall test.c -o test.o
, I gotI definitely think that the
-Wwrite-strings
compiler option is the one you want to enable to warn you about this sort of problem.