为什么将字符串文字传递到 char* 参数有时只会出现编译器错误?

发布于 2024-08-31 00:45:35 字数 1191 浏览 5 评论 0原文

我正在从事 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

笛声青案梦长安 2024-09-07 00:45:35

该标准指定了一条特殊规则,允许文字到 char* 的转换,从而悄悄地删除 const 限定。 (4.2/2):

不是宽字符串文字的字符串文字 (2.13.4) 可以转换为“指向 char 的指针”类型的右值;宽字符串文字可以转换为“指向 wchar_t 的指针”类型的右值。无论哪种情况,结果都是指向数组第一个元素的指针。仅当存在显式适当的指针目标类型时才考虑此转换,而不是在一般需要从左值转换为右值时考虑。 [注意:此转换已弃用。参见附件D。]

C++0x 标准进一步取消了这一弃用……这条无意义的规则已从即将发布的标准中完全删除。

const char*char* 错误必须是首先将文字转换为 const char* 的结果。

The standard specifies a special rule allowing the literal-to-char* conversion which quietly drops const qualification. (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”; a wide string literal can be converted to an rvalue of type “pointer to 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. ]

The C++0x standard takes that deprecation further… this nonsense rule is removed entirely from the upcoming standard.

The const char* to char* error must be a result of converting a literal to a const char* first.

多情出卖 2024-09-07 00:45:35

在 C++ 中使用字符串文字来初始化 char * 指针是一项已弃用的功能,但它仍然是合法的。这不是一个错误。您有责任确保不会通过此类指针进行任何修改尝试。

换句话说,您一定误解了之前遇到的编译错误。我认为您在此类初始化/分配中不会遇到任何错误。您在问题中提到的“无法将 const char* 转换为 char*”错误一定是由其他原因产生的。

请注意,您可以使用字符串文字初始化 char * 指针这一事实并不意味着您可以使用任意 const char * 值来初始化 char * 指针。这段代码

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

会产生错误,而这则

char *p = "A";

不会。上述已弃用的功能仅适用于字符串文字,不适用于所有 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 arbitrary const char * value to initialize a char * pointer. 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.

挽清梦 2024-09-07 00:45:35

我完全同意其他答案,我只是想补充一点,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

< em>但是,如果你打开优化器,就不会崩溃:

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”值初始化的,但它不是与字符串表完全相关,并且更改该字符串是完全合法的。

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):

#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. But when I declare char foo[] = "bar" it doesn't

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).
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.

天涯沦落人 2024-09-07 00:45:35

这实际上取决于你如何“经历并做出大量改变来解决这些问题”。

如果您只是将字符串文字向下转换为 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 a const so that the compiler can check these for you.

深爱成瘾 2024-09-07 00:45:35

回答为什么这种转换是合法的(尽管已弃用)的问题。曾经有一段时间,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.

烟燃烟灭 2024-09-07 00:45:35

由于 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:我测试了您的程序的以下简化版本:

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:警告:传递“FindArgDefault”的参数 1 会丢弃指针目标类型中的限定符

test.c:10:警告:传递“FindArgDefault”的参数 2 会丢弃指针目标类型中的限定符

我绝对认为 -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-constant char*. 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, the const 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 use const char* instead of char* for the return and parameter types. This should cause the compiler to complain when the return value is assigned to a char* (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 a char*, 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 say if (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:

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.

When I compiled with gcc -Wwrite-strings -Wall test.c -o test.o, I got

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

I definitely think that the -Wwrite-strings compiler option is the one you want to enable to warn you about this sort of problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文