从字符串常量到'char *'的弃用转换


Answers:


26

尽我所能,我将提供一些背景技术信息来说明此错误的原因和原因。

我将检查初始化C字符串的四种不同方法,并查看它们之间的区别。这些是有问题的四种方法:

char *text = "This is some text";
char text[] = "This is some text";
const char *text = "This is some text";
const char text[] = "This is some text";

现在,我要将第三个字母“ i”更改为“ o”,使其变为“ Thos is some text”。在所有情况下(您会认为),都可以通过以下方式实现:

text[2] = 'o';

现在,让我们看一下声明字符串的每种方式以及该text[2] = 'o';声明将如何影响事物。

首先是最常见的方式:char *text = "This is some text";。这从字面上是什么意思?好吧,在C语言中,它的字面意思是“创建一个名为的变量text,该变量是对此字符串文字的读写指针,该字符串文字存储在只读(代码)空间中”。如果您-Wwrite-strings打开了该选项,则会收到警告,如上面的问题所示。

基本上,这意味着“警告:您已尝试将一个读写变量指向无法写入的区域”。如果您尝试将第三个字符设置为“ o”,则实际上您将尝试写入一个只读区域,并且情况会变糟。在装有Linux的传统PC上,结果为:

分段故障

现在第二个:char text[] = "This is some text";。从字面上看,在C语言中,这意味着“创建一个类型为“ char”的数组,并使用数据“ This is some text \ 0”对其进行初始化。该数组的大小将足以存储数据。这样实际上就分配了RAM,并在运行时将值“ This is some text \ 0”复制到其中。没有警告,没有错误,完全有效。正确的方法是编辑数据。让我们尝试运行命令text[2] = 'o'

这是一些文字

效果很好。好。

现在的第三条道路:const char *text = "This is some text";。再次字面意思:“创建一个名为变量‘文本’这是一个只读指向这个数据在只读存储器中。” 注意,指针和数据现在都是只读的。没有错误,没有警告。如果尝试运行测试命令会怎样?好吧,我们不能。编译器现在很聪明,并且知道我们正在尝试做一些不好的事情:

错误:分配了只读位置'*(text + 2u)'

它甚至不会编译。尝试写入只读存储器现在受到保护,因为我们已经告诉编译器我们的指针是只读存储器。当然,它不会被指向只读存储器,但如果你点它来读写存储器(RAM)内存将仍然由编译器被写入保护。

最后是最后一种形式:const char text[] = "This is some text";。再次,像以前一样,[]它在RAM中分配一个数组并将数据复制到其中。但是,现在这是一个只读数组。您无法写入它,因为指向它的指针标记为const。尝试写入它会导致:

错误:分配了只读位置'*(text + 2u)'

因此,我们简要概述一下:

此表格完全无效,应不惜一切代价避免使用。它为各种不良事件打开了大门:

char *text = "This is some text";

如果您想使数据可编辑,则此表格是正确的表格:

char text[] = "This is some text";

如果您希望不编辑字符串,则此表单是正确的表单:

const char *text = "This is some text";

这种形式似乎浪费了RAM,但确实有其用途。最好暂时忘记它。

const char text[] = "This is some text";

6
值得注意的是,在Arduino上(至少是基于AVR的),字符串文字存在于RAM中,除非您使用PROGMEMPSTR()或宏声明它们F()。因此,const char text[]使用的RAM不超过const char *text
Edgar Bonet 2015年

Teensyduino和许多其他最近的arduino-compatibles会自动将字符串文字放在代码空间中,因此值得检查板上是否需要F()。
Craig.Feied

@ Craig.Feied通常无论如何都应使用F()。那些“不需要”它的人倾向于将其定义为简单的(const char *)(...)转换。如果板子不需要它,则没有真正的效果,但是如果您将代码移植到需要的板上,则可以节省很多。
Majenko

5

为了详细说明Makenko的出色答案,编译器会为此警告您有充分的理由。让我们做一个测试草图:

char *foo = "This is some text";
char *bar = "This is some text";

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo [2] = 'o';     // change foo only
  Serial.println (foo);
  Serial.println (bar);
  }  // end of setup

void loop ()
  {
  }  // end of loop

这里有两个变量,foo和bar。我在setup()中修改了其中之一,但是看到的结果是:

Thos is some text
Thos is some text

他们变了!

实际上,如果我们查看警告,则会看到:

sketch_jul14b.ino:1: warning: deprecated conversion from string constant to char*’
sketch_jul14b.ino:2: warning: deprecated conversion from string constant to char*’

编译器知道这是狡猾的,这是正确的!这样做的原因是,编译器(合理地)期望字符串常量不变(因为它们是常量)。因此,如果您"This is some text"在代码中多次引用字符串常量,则可以为所有字符串分配相同的内存。现在,如果您修改其中的一个,就可以全部修改!


天呐!谁会知道...对于最新的ArduinoIDE编译器来说是否仍然如此?我只是在ESP32上尝试过,它会导致重复的GuruMeditation错误。
not2qubit

@ not2qubit我刚刚在Arduino 1.8.9上测试过,这是真的。
尼克·加蒙

出现警告是有原因的。这次我得到:警告:ISO C ++禁止将字符串常量转换为'char '[-Wwrite-strings] char bar =“这是一些文本”;-禁忌是一个很强的词。由于禁止这样做,因此编译器可以随意修改并在两个变量之间共享相同的字符串。不要做违禁的事情!(此外,请阅读并消除警告)。:)
尼克·加蒙

因此,如果您遇到像这样糟糕的代码,并希望度过这一天。*foo*bar使用不同的字符串“ constants”进行初始声明是否可以防止这种情况发生?此外,如何从不把任何条件可言,这样的不同:char *foo;
not2qubit

1
不同的常量可能会有所帮助,但我个人不会在此放置任何内容,以后再以通常的方式在其中放置数据(例如new,使用strcpy和和delete)。
尼克·


3

例:

void foo (char * s)
  {
  Serial.println (s);
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo ("bar");
  }  // end of setup

void loop ()
  {
  }  // end of loop

警告:

sketch_jul14b.ino: In function ‘void setup()’:
sketch_jul14b.ino:10: warning: deprecated conversion from string constant to ‘char*’

该函数foo需要一个char *(因此可以修改),但是您传递的是字符串文字,不应对其进行修改。

编译器警告您不要这样做。不建议使用它,它可能在将来的编译器版本中从警告变为错误。


解决方案:使foo接受const char *:

void foo (const char * s)
  {
  Serial.println (s);
  }

我不明白 你是说不能修改?

较旧的C(和C ++)版本使您可以像上面的示例一样编写代码。您可以创建一个函数(如foo),该函数先打印传递给它的内容,然后传递文字字符串(例如foo ("Hi there!");

但是,char *允许将以参数为参数的函数修改其参数(即,Hi there!在这种情况下进行修改)。

您可能已经写过,例如:

void foo (char * s)
  {
  Serial.println (s);
  strcpy (s, "Goodbye");
  }

不幸的是,通过传递文字,您现在可能已经修改了该文字,以便“嗨!” 现在是“再见”,这不好。实际上,如果您复制了更长的字符串,则可能会覆盖其他变量。或者,在某些实现中,您会遇到访问冲突,因为“嗨!” 可能已放入只读(受保护的)RAM中。

因此,编译器-编写器正在逐渐弃用此用法,以便您传递文字的函数必须将该参数声明为const


如果不使用指针,是否有问题?
Federico Corazza 2015年

什么样的问题?该警告特别是关于将字符串常量转换为char *指针。你能详细说明吗?
尼克·加蒙

@Nick:您的意思是“(..)您正在传递字符串文字,不应对其进行修改”。我不明白 你的意思是can not被修改?
Mads

我修改了答案。马延科在他的回答中涵盖了大多数观点。
尼克·加蒙

1

我有这个编译错误:

TimeSerial.ino:68:29: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]
   if(Serial.find(TIME_HEADER)) {

                         ^

请替换此行:
#define TIME_HEADER "T" // Header tag for serial time sync message

用这一行:
#define TIME_HEADER 'T' // Header tag for serial time sync message

编译顺利。


3
这种变化改变了从一个字符串“T”的定义为一个字符用ASCII码为信资本T.价值
DLU
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.