...Y tu me dices de no utilizar la sprintf? A mí?... |
Afortunadamente viene en nuestro auxilio la snprintf(), que es de la misma familia, pero más segura. Vemos los dos prototipos en comparación:
int sprintf(char *str, const char *format, ...); int snprintf(char *str, size_t size, const char *format, ...);La snprintf() nos obliga a poner el size del buffer como segundo argumento, entonces es muy fácil coger la costumbre de escribir de una manera error-free como esta:
char buffer[32]; snprintf(buffer, sizeof(buffer), "Hello world!");Si en lugar de "Hello world!" hubiéramos escrito una cadena de más de 32 chars, ningún problema: la snprintf() trunca la cadena de manera adecuada y estamos a salvo.
Y ahora os propongo un pequeño ejemplo real: tomamos una nuestra vieja conocida escrita para un viejo post, la getDateUsec() y la vamos a escribir en dos versiones, una buena y otra mala (bad). Veamos:
#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <time.h> // prototipos locales char *getDateUsec(char *dest, size_t size); char *badGetDateUsec(char *dest); // función main int main(int argc, char* argv[]) { // llama getDateUsec (o badGetDateUsec) y escribe el resultado char dest[12]; printf("fecha con usec: %s\n", getDateUsec(dest, sizeof(dest))); //printf("fecha con usec: %s\n", badGetDateUsec(dest)); return EXIT_SUCCESS; } // getDateUsec() - Genera una cadena con fecha y hora (usa los microsegundos) char *getDateUsec(char *dest, size_t size) { // get time (con gettimeofday()+localtime() en lugar de time()+localtime() para obtener los usec) struct timeval tv; gettimeofday(&tv, NULL); struct tm *tmp = localtime(&tv.tv_sec); // format cadena destinación dest(debe ser alocada por el llamante) y añade los usec char fmt[128]; strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp); snprintf(dest, size, fmt, tv.tv_usec); // return cadena destinacion dest return dest; } // badGetDateUsec() - Genera una cadena con fecha y hora (usa los microsegundos) (versione bad) char *badGetDateUsec(char *dest) { /// get time (con gettimeofday()+localtime() en lugar de time()+localtime() para obtener los usec) struct timeval tv; gettimeofday(&tv, NULL); struct tm *tmp = localtime(&tv.tv_sec); // format cadena destinación dest(debe ser alocada por el llamante) y añade los usec char fmt[128]; strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp); sprintf(dest, fmt, tv.tv_usec); // return cadena destinacion dest return dest; }Vale, en aquel entonces, para simplificar, había escrito una getDateUsec() que era, de hecho, una badGetDateUsec() (y más tarde, por la precisión, procedí en modificarla en el post). Esa versión funcionaba, pero podía crear problemas, mientras que la nueva versión es mucho más segura. Intentad compilar el ejemplo, adonde, deliberadamente, he subdimensionado el buffer de destino: comentando badGetDateUsec() y usando la getDateUsec(), funciona perfectamente, truncando el output a 12 chars. Si, sin embargo, se comenta la getDateUsec() y se utiliza la badGetDateUsec() el programa peta mientras se ejecuta. !Inténtelo!
Y ya que estamos en el argumento sprintf() un pequeño consejo un poco OT: si necesitáis agregar secuencialmente una cadena (en un bucle, por ejemplo) sobre una cadena base (para redactar un texto, por ejemplo) no hacerlo nunca así:
char buf[256] = ""; for (int i = 0; i < 5; i++) sprintf(buf, "%s añadido a la cadena %d\n", buf, i);el método aquí arriba parece funcionar, pero, en realidad, funciona cuando le da la gana. Hacerlo, en cambio, así:
char buf[256] = ""; for (int i = 0; i < 5; i++) { char tmpbuf[256]; sprintf(tmpbuf, "%s añadido a la cadena %d\n", buf, i); sprintf(buf, "%s", tmpbuf); }Y si no me creéis probad a verificar el código con un lint como cppchek (que siempre es una buena idea) o consultad el manual de la sprintf():
C99 and POSIX.1-2001 specify that the results are undefined if a call to sprintf(), snprintf(), vsprintf(), or vsnprintf() would cause copy‐ ing to take place between objects that overlap (e.g., if the target string array and one of the supplied input arguments refer to the same buffer).Y, por supuesto, también en este último ejemplo (hecho, por simplicidad, con la sprintf ()) seria recomendable usar la snprintf ().
¡Hasta el próximo post!