Cuidado con la función "sprintf"

Hola a todos!.

Estoy con un proyecto entre manos y estaba jugando un poco y me he encontrado con un error en la función sprintf.

Os pongo el ejemplo de fallo.

/*
 * EJEMPLO DE FALLO DE SPRINTF.
 * El reloj simulado se vuelve loco y se acelera.
 */
#include <LiquidCrystal.h>
LiquidCrystal lcd(13,12,5,4,3,2);

struct DATETIME {
  uint8_t year;
  uint8_t month;
  uint8_t day;
  uint8_t hour;
  uint8_t minute;
  uint8_t second;
};

uint32_t t;
char line[16]; // En esta línea se pinta la información.
DATETIME now = { 24, 10, 17, 16, 41, 0 };

void setup() {
  lcd.begin(20,4);
}

void loop() {
  // Simulo un reloj, tan solo actualizo los segundos, minutos y horas.
  if ( millis()-t >= 1000 ) {
    now.second++;
    if( now.second>59 ) {
      now.second=0;
      now.minute++;
      if ( now.minute>59 ) {
        now.minute=0;
        now.hour++;
        if ( now.hour>23 ) {
          now.hour=0;
        }
      }
    }
    t = millis();
  }
  // Muestro la fecha formateada. Funciona, pero vuelve loco al reloj.
  sprintf(line, "%02d/%02d/%02d %02d:%02d:%02d", now.day, now.month, now.year, now.hour, now.minute, now.second);
  lcd.setCursor(0,0); lcd.print(line);  
}

Básicamente es un reloj que muestra la fecha formateada (DD/MM/YY hh:mm:ss) en la pantalla del LCD, y el formateo se hace correctamente pero el reloj se acelera solo!!

Creo que no le gusta mucho que la variable line sea global y que si llamas a dicha función en el loop "machaca" la variable que le pases. ¿Por qué? Porque al introducir
el código en una función y eliminar la variable global line deja de dar problemas:

/*
 * EJEMPLO DE SPRINTF CORREGIDO
 */
#include <LiquidCrystal.h>
LiquidCrystal lcd(13,12,5,4,3,2);

struct DATETIME {
  uint8_t year;
  uint8_t month;
  uint8_t day;
  uint8_t hour;
  uint8_t minute;
  uint8_t second;
};

uint32_t t;
DATETIME now = { 24, 10, 17, 16, 41, 0 };

void muestraFecha() {
  // Muestro la fecha formateada. Funciona, pero vuelve loco al reloj.
  char line[16];
  sprintf(line, "%02d/%02d/%02d %02d:%02d:%02d", now.day, now.month, now.year, now.hour, now.minute, now.second);
  lcd.setCursor(0,0); lcd.print(line);  
}

void setup() {
  lcd.begin(20,4);
}

void loop() {
  // Simulo un reloj, tan solo actualizo los segundos, minutos y horas.
  if ( millis()-t >= 1000 ) {
    now.second++;
    if( now.second>59 ) {
      now.second=0;
      now.minute++;
      if ( now.minute>59 ) {
        now.minute=0;
        now.hour++;
        if ( now.hour>23 ) {
          now.hour=0;
        }
      }
    }
    t = millis();
    muestraFecha();
  }
}

Eso unido al problema de que no trabaja bien con float me lleva a recomendaros que si podéis evitar usar esta función, mejor que mejor, ya que puede provocar problemas e incluso colgar el Arduino.

La verdad es que es una pena, porque para formatear cadenas es muy potente y cómoda.

Uy!, me acabo de dar cuenta que lo publiqué en software y no en documentación. Y tampoco hice ninguna consulta. Dejo al moderador si quiere cambiarlo a documentación. Aun así me gustaría que alguien compartiese problemas que haya tenido con esta función en este post.

Lamento decepcionarte pero en ambos códigos hay un error en el dimensionamiento del array line y eso provoca el mal funcionamiento.
Defines

char line[16];

Pero son 18 caracteres sumando el caracter \0 de terminación (aunque no me queda claro si en casos como este hace falta entonces siempre le dejo el lugar, por si acaso :wink: )

dd/mm/yy hh:mm:ss\0
12345678911111111 1
         01234567 8

Entonces debería ser

char line[18];  // con 17 también va bien

Y fin del problema.

La gran pregunta es ¿a qué se deberá que el array local no provoca el error a pesar de estar subdimensionado?

1 Like

Bueno iba con el mismo comentario que @MaximoEsfuerzo y es fácil confundirse con eso.
Justamente venía a defender a sprintf, función que me gusta y uso siempre.
No tolero la cantidad de if >10 que usan para poner 1 o 2 digitos en seg, min, hora, etc. En cambio un simple "%02d" resuelve todo dentro de sprintf claro está.

Ves, me estoy volviendo tonto por minutos :smile: :smiley: :grinning: :rofl: Mira que olvidar una cosa tan obvia!!!

Supongo que cuando declaras las variables globales, line está antes de now y reserva espacio en RAM para el array uno al lado del otro, por eso cuando llamas a la función sprintf y el resultado es mayor que el reservado para el array machaca las posiciones de RAM de la variable now. Sin embargo al hacerlo dentro de la función cada vez que se invoca esta crea en la RAM el espacio necesario para el array en otra posición diferente no machacando nada.

El problema puede venir después cuando tengas más código y mas funciones, donde si se pueda machacar.

Y, efectivamente, tener que formatear una cadena ya sea por fechas, números o similar es un petardo a base de "if" con lo fácil que es sprintf.

Cambio mi recomendación por lo siguiente: No dejéis de programar que al final hasta las cosas mas obvias se olvidan!!!

Uds habran visto que yo uso mucho sprintf. En los displays LCD, OLED o TFT.
Me gusta tener controlado que presento y la forma formateada que tiene es ideal.
Claro que los Arduinos no pueden manejar los float (en realidad si, pero es otro tema) asi que por esa razón prefiero usar todo aquello que me simplifica la vida.
Esto por ejemplo

sprintf(buffer, "%3.1f", variable_float);

a mi me parece fantástico.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.