为啥将字符串文字传递给 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 -ansichar *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 在一种情况下允许将文字传递给按引用传递的参数,而在其他情况下不允许?

为啥我们不能将字符串作为模板参数传递?

为啥将“switch”类型参数的值传递给字符串参数?

如何将 Swift 字符串数组传递给采用 char ** 参数的 C 函数

适合在函数参数中将字符串文字转换为 char * 吗?

为啥仅在某些情况下才能将字符串文字隐式转换为 char* ? [复制]