En su libro The Practice of Programming (que vale la pena leer), Kernighan y Pike discuten este problema y lo resuelven utilizando snprintf()
para crear la cadena con el tamaño de búfer correcto para pasar a la scanf()
familia de funciones. En efecto:
int scanner(const char *data, char *buffer, size_t buflen)
{
char format[32];
if (buflen == 0)
return 0;
snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
return sscanf(data, format, buffer);
}
Tenga en cuenta que esto aún limita la entrada al tamaño proporcionado como 'búfer'. Si necesita más espacio, debe realizar la asignación de memoria o utilizar una función de biblioteca no estándar que realice la asignación de memoria por usted.
Tenga en cuenta que la versión POSIX 2008 (2013) de la scanf()
familia de funciones admite un modificador de formato m
(un carácter de asignación asignación) para las entradas de cadena ( %s
, %c
, %[
). En lugar de tomar un char *
argumento, toma un char **
argumento y asigna el espacio necesario para el valor que lee:
char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
printf("String is: <<%s>>\n", buffer);
free(buffer);
}
Si la sscanf()
función no satisface todas las especificaciones de conversión, toda la memoria que asignó para %ms
conversiones similares se libera antes de que la función regrese.
buflen-1
- Gracias. Luego debe preocuparse por el subdesbordamiento sin firmar (envolviendo a un número bastante grande), de ahí laif
prueba. Me sentiría muy tentado a reemplazar eso con anassert()
, o respaldarlo con unassert()
antes deif
que se active durante el desarrollo si alguien es lo suficientemente descuidado como para pasar 0 como tamaño. No he revisado cuidadosamente la documentación para saber qué%0s
significasscanf()
: la prueba podría ser mejor comoif (buflen < 2)
.snprintf
escribe algunos datos en un búfer de cadena ysscanf
lee de esa cadena creada. ¿Dónde exactamente reemplaza estoscanf
en el sentido de que se lee en stdin?snprintf
pero no es el parámetro de formato real.data
y, porsscanf()
lo tanto, sean apropiados. Si desea leer desde la entrada estándar, elimine eldata
parámetro y llame en suscanf()
lugar. En cuanto a la elección del nombreformat
de la variable que se convierte en la cadena de formato en la llamada asscanf()
, tiene derecho a cambiarle el nombre si lo desea, pero su nombre no es inexacto. No estoy seguro de qué alternativa tiene sentido; ¿Lo dejaríain_format
más claro? No planeo cambiarlo en este código; puede hacerlo si usa esta idea en su propio código.scanf()
en macOS no está documentado como compatible%ms
, aunque sería útil.Si está usando gcc, puede usar el
a
especificador de extensión GNU para que scanf () asigne memoria para que usted mantenga la entrada:int main() { char *str = NULL; scanf ("%as", &str); if (str) { printf("\"%s\"\n", str); free(str); } return 0; }
Editar: como señaló Jonathan, debe consultar las
scanf
páginas de manual, ya que el especificador puede ser diferente (%m
) y es posible que deba habilitar ciertas definiciones al compilar.fuente
m
modificador para hacer el mismo trabajo. Verscanf()
. Deberá comprobar si los sistemas que utiliza admiten este modificador.%ms
. La notación%a
es sinónimo de%f
(en la salida, solicita datos de coma flotante hexadecimal). La página de manual de GNU parascanf()
dice: _ No está disponible si el programa está compilado congcc -std=c99
o gcc -D_ISOC99_SOURCE (a menos_GNU_SOURCE
que también se especifique), en cuyo casoa
se interpreta como un especificador para números de punto flotante (ver arriba) ._La mayoría de las veces una combinación de
fgets
ysscanf
hace el trabajo. La otra cosa sería escribir su propio analizador, si la entrada está bien formateada. También tenga en cuenta que su segundo ejemplo necesita un poco de modificación para usarse de manera segura:#define LENGTH 42 #define str(x) # x #define xstr(x) str(x) /* ... */ int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array);
Lo anterior descarta el flujo de entrada hasta pero sin incluir el carácter de nueva línea (
\n
). Deberá agregar ungetchar()
para consumir esto. También verifique si alcanzó el final de la transmisión:if (!feof(stdin)) { ...
y eso es todo.
fuente
feof
código en un contexto más amplio? Estoy preguntando porque esa función a menudo se usa mal.array
necesita serchar array[LENGTH+1];
El uso directo
scanf(3)
y sus variantes plantea una serie de problemas. Normalmente, los usuarios y los casos de uso no interactivos se definen en términos de líneas de entrada. Es raro ver un caso en el que, si no se encuentran suficientes objetos, más líneas resolverán el problema, pero ese es el modo predeterminado para scanf. (Si un usuario no sabía cómo ingresar un número en la primera línea, una segunda y una tercera línea probablemente no ayudarán).Al menos si
fgets(3)
sabe cuántas líneas de entrada necesitará su programa y no tendrá desbordamientos de búfer ...fuente
Limitar la longitud de la entrada es definitivamente más fácil. Puede aceptar una entrada arbitrariamente larga utilizando un bucle, leyendo un poco a la vez, reasignando espacio para la cadena según sea necesario ...
Pero eso es mucho trabajo, por lo que la mayoría de los programadores de C simplemente cortan la entrada en una longitud arbitraria. Supongo que ya lo sabe, pero el uso de fgets () no le permitirá aceptar cantidades arbitrarias de texto; todavía tendrá que establecer un límite.
fuente
realloc()
su búfer.No es mucho trabajo hacer una función que asigne la memoria necesaria para su cadena. Esa es una pequeña función c que escribí hace algún tiempo, siempre la uso para leer en cadenas.
Devolverá la cadena leída o si ocurre un error de memoria NULL. Pero tenga en cuenta que debe liberar () su cadena y siempre verificar su valor de retorno.
#define BUFFER 32 char *readString() { char *str = malloc(sizeof(char) * BUFFER), *err; int pos; for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++) { if(pos % BUFFER == BUFFER - 1) { if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL) free(str); str = err; } } if(str != NULL) str[pos] = '\0'; return str; }
fuente
sizeof (char)
es por definición1
. No lo necesitas aquí.strerror(3)
) o esperar que se pase una cadena preasignada (como (strerror_r(3)
- oscanf(3)
) ...