conversión obsoleta de constante de cadena a 'char *'

16

¿Qué significa este error? No puedo resolverlo de ninguna manera.

advertencia: conversión obsoleta de constante de cadena a 'char *' [-Wwrite-strings]

Federico Corazza
fuente
Esta pregunta debería estar en StackOverflow, no en Arduino :)
Vijay Chavda

Respuestas:

26

Como es mi costumbre, voy a proporcionar un poco de información técnica de fondo sobre por qué y por qué este error.

Voy a inspeccionar cuatro formas diferentes de inicializar cadenas C y veré cuáles son las diferencias entre ellas. Estas son las cuatro formas en cuestión:

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";

Ahora, para esto, voy a querer cambiar la tercera letra "i" por una "o" para que sea "Thos is some text". Eso podría, en todos los casos (se podría pensar), lograrlo:

text[2] = 'o';

Ahora veamos qué hace cada forma de declarar la cadena y cómo esa text[2] = 'o';declaración afectaría las cosas.

En primer lugar la forma más comúnmente visto: char *text = "This is some text";. ¿Qué significa esto literalmente? Bueno, en C, literalmente significa "Crear una variable llamada textque es un puntero de lectura-escritura a este literal de cadena que se mantiene en el espacio de solo lectura (código)". Si tiene la opción -Wwrite-stringsactivada, recibirá una advertencia como se ve en la pregunta anterior.

Básicamente eso significa "Advertencia: ha intentado crear una variable que sea punto de lectura-escritura en un área en la que no puede escribir". Si intenta y luego establece el tercer carácter en "o", de hecho estaría intentando escribir en un área de solo lectura y las cosas no serán agradables. En una PC tradicional con Linux que da como resultado:

Fallo de segmentación

Ahora la segunda: char text[] = "This is some text";. Literalmente, en C, eso significa "Crear una matriz de tipo" char "e inicializarla con los datos" Este es un texto \ 0 ". El tamaño de la matriz será lo suficientemente grande como para almacenar los datos". De modo que en realidad asigna RAM y copia el valor "Esto es texto \ 0" en tiempo de ejecución. Sin advertencias, sin errores, perfectamente válido. Y la forma correcta de hacerlo si desea poder editar los datos . Intentemos ejecutar el comando text[2] = 'o':

Es un texto

Funcionó perfectamente. Bueno.

Ahora la tercera vía: const char *text = "This is some text";. Nuevamente el significado literal: "Crear una variable llamada" texto "que sea un puntero de solo lectura a estos datos en la memoria de solo lectura". Tenga en cuenta que tanto el puntero como los datos ahora son de solo lectura. Sin errores, sin advertencias. ¿Qué sucede si intentamos ejecutar nuestro comando de prueba? Pues no podemos. El compilador ahora es inteligente y sabe que estamos tratando de hacer algo malo:

error: asignación de ubicación de solo lectura '* (texto + 2u)'

Ni siquiera se compilará. Intentar escribir en la memoria de solo lectura ahora está protegido porque le hemos dicho al compilador que nuestro puntero es la memoria de solo lectura. Por supuesto, no tiene que estar apuntando a la memoria de solo lectura, pero si lo señala a la memoria de lectura-escritura (RAM) esa memoria aún estará protegida contra la escritura del compilador.

Finalmente, el último formulario: const char text[] = "This is some text";. Nuevamente, como antes [], asigna una matriz en RAM y copia los datos en ella. Sin embargo, ahora se trata de una matriz de solo lectura. No puede escribir porque el puntero está etiquetado como const. Intentar escribir le da como resultado:

error: asignación de ubicación de solo lectura '* (texto + 2u)'

Entonces, un resumen rápido de dónde estamos:

Este formulario es completamente inválido y debe evitarse a toda costa. Abre la puerta a todo tipo de cosas malas que suceden:

char *text = "This is some text";

Este formulario es el correcto si desea que los datos sean editables:

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

Este formulario es el correcto si desea cadenas que no se editarán:

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

Esta forma parece un desperdicio de RAM, pero tiene sus usos. Sin embargo, es mejor olvidarlo por ahora.

const char text[] = "This is some text";
Majenko
fuente
66
Vale la pena señalar que en los Arduinos (al menos los basados ​​en AVR), los literales de cadena viven en RAM, a menos que los declare con una macro como PROGMEM, PSTR()o F(). Por lo tanto, const char text[]no usa más RAM que const char *text.
Edgar Bonet
Teensyduino y muchos otros arduino-compatibles más recientes colocan automáticamente literales de cadena en el espacio de código, por lo que vale la pena verificar si F () es necesario o no en su placa.
Craig.Feied
@ Craig.Feied En general, F () debe usarse independientemente. Aquellos que no lo "necesitan" tienden a definirlo como un simple (const char *)(...)casting. Sin efecto real si la placa no lo necesita, pero un gran ahorro si luego transfiere su código a una placa que sí lo necesita.
Majenko
5

Para profundizar en la excelente respuesta de Makenko, hay una buena razón por la cual el compilador le advierte sobre esto. Hagamos un boceto de prueba:

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

Tenemos dos variables aquí, foo y bar. Modifico uno de esos en setup (), pero veo cuál es el resultado:

Thos is some text
Thos is some text

¡ Ambos se cambiaron!

De hecho, si miramos las advertencias, vemos:

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

El compilador sabe que esto es dudoso, ¡y es correcto! La razón de esto es que el compilador (razonablemente) espera que las constantes de cadena no cambien (ya que son constantes). Por lo tanto, si se refiere a la cadena constante "This is some text"varias veces en su código, se le permite asignar la misma memoria a todos ellos. ¡Ahora si modifica uno, los modifica a todos!

Nick Gammon
fuente
Santo humo! ¿Quién hubiera sabido ... ¿Esto sigue siendo cierto para los últimos compiladores ArduinoIDE? Acabo de probarlo en un ESP32 y causa errores repetidos de GuruMeditation .
not2qubit
@ not2qubit Acabo de probar en Arduino 1.8.9 y es cierto allí.
Nick Gammon
Las advertencias están ahí por una razón. Esta vez recibí: advertencia: ISO C ++ prohíbe convertir una constante de cadena a 'char ' [-Wwrite-strings] char bar = "Esto es algo de texto"; - Prohibir es una palabra fuerte. Como se le prohíbe hacer eso, el compilador es libre de manipular y compartir la misma cadena sobre dos variables. ¡No hagas cosas prohibidas ! (Además, lea y elimine las advertencias). :)
Nick Gammon
Entonces, en caso de que te encuentres con un código horrible como este y quieras sobrevivir el día. ¿Una declaración inicial de *fooy *barusando diferentes "constantes" de cadenas evitaría que esto suceda? Además, ¿cómo es esto diferente de no poner ninguna secuencia en absoluto, como char *foo;:?
not2qubit
1
Diferentes constantes pueden ayudar, pero personalmente no pondría nada allí, y pondría datos allí de la manera habitual más tarde (por ejemplo new, con strcpyy delete).
Nick Gammon
4

Deje de intentar pasar una constante de cadena donde una función toma un char*, o cambie la función para que tome un const char*lugar.

Cadenas como "cadena aleatoria" son constantes.

Ignacio Vazquez-Abrams
fuente
¿Es un texto como "caracteres aleatorios" un carácter constante?
Federico Corazza
1
Los literales de cadena son constantes de cadena.
Ignacio Vazquez-Abrams
3

Ejemplo:

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

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

void loop ()
  {
  }  // end of loop

Advertencia:

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

La función fooespera un char * (que, por lo tanto, puede modificar) pero está pasando un literal de cadena, que no debe modificarse.

El compilador le advierte que no haga esto. Al quedar en desuso, podría convertirse de una advertencia en un error en una futura versión del compilador.


Solución: haga que foo tome un constante char *:

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

No lo entiendo ¿Quieres decir que no puede puede modificar?

Las versiones anteriores de C (y C ++) le permiten escribir código como en mi ejemplo anterior. Podrías hacer una función (comofoo ) que imprima algo que le pasa, y luego pasar una cadena literal (por ejemplo foo ("Hi there!");) .

Sin embargo, una función que toma char * como argumento puede modificar su argumento (es decir, modificar Hi there!en este caso).

Es posible que haya escrito, por ejemplo:

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

Desafortunadamente, al pasar un literal, ahora ha modificado potencialmente ese literal para que "¡Hola!" ahora es "Adiós" que no es bueno. De hecho, si copió en una cadena más larga, podría sobrescribir otras variables. O, en algunas implementaciones, obtendría una infracción de acceso porque "¡Hola!" podría haberse colocado en RAM de solo lectura (protegida).

Por lo tanto, los compiladores-escritores están despreciando gradualmente este uso, de modo que las funciones a las que se pasa un literal, deben declarar ese argumento como const.

Nick Gammon
fuente
¿Es un problema si no uso un puntero?
Federico Corazza
Que tipo de problema Esa advertencia particular se trata de convertir una cadena constante en un puntero char *. ¿Puedes elaborar?
Nick Gammon
@Nick: ¿Qué quieres decir con "(..) estás pasando un literal de cadena, que no debe modificarse". No lo entiendo ¿Quieres decir can notser modificado?
Mads Skjern
Modifiqué mi respuesta. Majenko cubrió la mayoría de estos puntos en su respuesta.
Nick Gammon
1

Tengo este error de compilación:

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

                         ^

Por favor reemplace esta línea:
#define TIME_HEADER "T" // Header tag for serial time sync message

con esta linea:
#define TIME_HEADER 'T' // Header tag for serial time sync message

y la compilación va bien.

Ginebra
fuente
3
Este cambio cambia la definición de una cadena de caracteres "T" a un solo carácter con el valor del código ASCII para la letra mayúscula T.
dlu