为啥将字符串文字传递给 char* 参数有时只是编译器错误?
Posted
技术标签:
【中文标题】为啥将字符串文字传递给 char* 参数有时只是编译器错误?【英文标题】:Why is passing a string literal into a char* argument only sometimes a compiler error?为什么将字符串文字传递给 char* 参数有时只是编译器错误? 【发布时间】:2011-02-15 04:09:18 【问题描述】:我正在使用 C 和 C++ 程序。我们曾经在没有 make-strings-writable 选项的情况下进行编译。但那会收到一堆警告,所以我把它关掉了。
然后我收到一大堆错误,形式为“无法在函数 foo 的参数 3 中将 const char* 转换为 char*”。所以,我经历了很多改变来解决这些问题。
然而,今天,程序崩溃了,因为文字“”被传递到一个期待 char* 的函数中,并将第 0 个字符设置为 0。它没有做任何坏事,只是试图编辑一个不断的,崩溃的。
我的问题是,为什么这不是编译器错误?
如果重要的话,这是在使用 gcc-4.0 编译的 mac 上。
编辑:添加代码:
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 char *。将它们视为 char * 会使您面临这些问题。 @matteo:当然。我的问题是为什么编译器会捕获其中一些,但不是全部... 您添加的代码的行为完全合乎逻辑。是的,这应该编译并运行,直到您尝试修改某些内容。但是,据我了解,您的主要问题是编译器检测到一些错误而未能检测到其他一些错误。这是其中一个“失败”的。现在向我们展示一个成功“检测到”的。 那里有三个函数。调用一个函数来返回一个指向字符串文字的指针并将其分配给char *
,然后调用一个函数来修改它。您并没有让编译器轻松找出正在发生的事情。致命的转换是调用FindArgDefault()
,其他一切都是正确的。有很多旧代码不是 const 正确的,根据标准,允许将字符串文字传递给 char *
(尽管尝试更改它的未定义行为)。我认为你对 gcc 的期望太高了。
【参考方案1】:
由于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 收到的错误消息的更多信息后,我删除了原始帖子中偏离主题的部分并添加了一些额外的 cmets。
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: 警告:传递 'FindArgDefault' 的 arg 1 会丢弃来自指针目标类型的限定符
test.c:10: 警告:传递 'FindArgDefault' 的 arg 2 会丢弃来自指针目标类型的限定符
我绝对认为-Wwrite-strings
编译器选项是您想要启用以警告您此类问题的选项。
【讨论】:
我没有禁用 -Wwrite-strings。我禁用了-fwritable-stings。 p 不是空指针。 p 是指向空字符串的指针。 q 是 char* 不是 char,所以 q = "",而不是 '\0' 。是的,这个函数很容易受到空指针的影响,但我认为我们很清楚这没有发生......虽然保护它不会有什么坏处,但这不是这里发生的事情。 .. @Brian- 你是对的。这是我最后一次尝试在电话会议上阅读复杂问题并发布解决方案,我保证。我已编辑我的答案以更准确地解决问题。 我不太确定 delim 的作用。它最初不是我的代码......并且 findargdefault 中缺少的代码调用了另一个函数(在一个 if 分支中,在它崩溃的情况下不被采用)并且返回不会暂时返回到 const,所以改变那些 char*到 consts 不是一个好主意...【参考方案2】:回答为什么这种转换是合法的问题(尽管已弃用)。好吧,曾经有一段时间,C 语言中没有 const 关键字,人们在那段时间设法生成了一些代码。 C++ 的设计者一定已经意识到,通过破坏他们的代码来激怒这么多人并不是一个好主意。
【讨论】:
没错。这就是为什么 gcc 有-Wwrite-strings
选项但在默认情况下或 -Wall
不打开它的原因之一(它会在旧代码或非 const 正确代码上引起大量警告)。【参考方案3】:
我完全同意其他答案,我只想补充一点,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”值初始化的,但它不是与字符串表完全相关,更改该字符串是完全合法的。
【讨论】:
【参考方案4】:该标准指定了一个特殊规则,允许文字到char*
的转换,它悄悄地放弃了const
的资格。 (4.2/2):
不是宽字符串文字的字符串文字(2.13.4)可以转换为“pointer to char”类型的右值;宽字符串文字可以转换为“指向 wchar_t 的指针”类型的右值。无论哪种情况,结果都是指向数组第一个元素的指针。仅当存在显式适当的指针目标类型时才考虑这种转换,而不是当一般需要从左值转换为右值时。 [注意:此转换已弃用。见附录 D。]
C++0x 标准进一步弃用了这一点……这条废话规则已从即将发布的标准中完全删除。
const char*
到 char*
的错误必须是首先将文字转换为 const char*
的结果。
【讨论】:
我不确定那句话是什么意思。执行此操作时出现编译器错误: char* foo = "bar";但不是当我做 char foo[] = "bar"; @Brian:char * foo = "bar";
创建一个指向非 const char 的指针,并将其指向 const char。 char foo[] = "bar";
创建一个 char 的四字符数组,用 'b'、'a'、'r'、'\0' 初始化。这些语句做了完全不同的事情。
@Brian:对我来说,在 OS X 10.6.3 上使用 g++ -V 4.0.1 -Wall -ansi
,char *foo = "bar";
在命名空间或函数范围内编译而不会出现警告。使用默认的 4.2.1 会产生警告。
@david,没错。 @Potatoswatter,这可能是gcc和g ++之间的区别吗? (即,在 C 和 C++ 之间)实际上可以解释一切......
@Brian:如果您尝试通过 GCC 而不是 G++ 运行 C++ 代码,您应该会看到链接错误。如果您使用的是 XCode,我认为它不会让您犯这个错误,也不会出现任何其他受人尊敬的 IDE。【参考方案5】:
在 C++ 中使用字符串文字来初始化 char *
指针是一个已弃用的功能,但它仍然是合法的。这不是错误。您有责任确保不会通过此类指针进行任何修改尝试。
换句话说,您对之前遇到的编译错误一定有一些误解。我认为您从未对此类初始化/分配有任何错误。您在问题中提到的“无法将 const char* 转换为 char*”错误一定是由其他东西产生的。
请注意,您可以使用字符串字面量初始化char *
指针这一事实并不意味着您可以使用任意const char *
值来初始化char *
指针。这段代码
const char *pc = "A";
char *p = pc;
会产生错误,而这
char *p = "A";
不会。上述已弃用的功能仅适用于字符串文字,不适用于所有 const char *
指针。
【讨论】:
我的问题不在于变量,而在于参数。当我声明一个 char* foo = "bar" 它实际上抱怨。但是当我声明 char foo[] = "bar" 它没有但我认为这是一个不相关的问题。 查看我对这个问题的回答的附录。 @Brian-char foo[] = "bar"
没有给您错误的原因是因为编译器将字符串视为数组初始值设定项。它没有声明指向常量字符串的指针(复制地址),而是声明了一个数组并用值“bar”初始化它(复制数据)。在这种情况下,foo
中的数据应该是可变的。这是数组/指针二元性的细微差别。【参考方案6】:
这真的取决于你如何“经历并做出很多改变来解决这些问题。”
如果您只是将字符串文字向下转换为char*
,那么您就是在告诉编译器不要捕获该错误。如果你要修改它,你需要制作一个副本。否则,声明您的函数接口以采用const
,以便编译器可以为您检查这些。
【讨论】:
嗯,问题在于这是我HADN'T修复它的情况,因为没有错误告诉我修复某些东西。它只是将 "" 传递给 char* 参数。以上是关于为啥将字符串文字传递给 char* 参数有时只是编译器错误?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 PHP 在一种情况下允许将文字传递给按引用传递的参数,而在其他情况下不允许?