¿Para qué sirve el especificador de formato% n en C?

125

¿Para qué sirve el %nespecificador de formato en C? ¿Alguien podría explicar con un ejemplo?

josh
fuente
25
¿Qué ha sido del bello arte de leer el excelente manual?
Jens
8
Creo que la verdadera pregunta es ¿cuál es el PUNTO de una opción como esta? ¿Por qué alguien querría saber el valor de los números de caracteres impresos y mucho menos escribir ese valor directamente en la memoria? Era como si los desarrolladores se aburrieran y decidieran introducir un error en el kernel
jia chen
Es por eso que Bionic lo soltó.
solidak
1
De hecho, es una pregunta válida, y una que los buenos manuales probablemente no responderán; se ha descubierto que %nlas marcas printfaccidentalmente Turing completo y se puede aplicar por ejemplo Brainfuck en ella, ver github.com/HexHive/printbf y oilshell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer

Respuestas:

147

Nada impreso El argumento debe ser un puntero a un int con signo, donde se almacena el número de caracteres escritos hasta ahora.

#include <stdio.h>

int main()
{
  int val;

  printf("blah %n blah\n", &val);

  printf("val = %d\n", val);

  return 0;

}

Se imprime el código anterior:

blah  blah
val = 5
Starkey
fuente
1
Menciona que el argumento debe ser un puntero a un int con signo, luego usó un int sin signo en su ejemplo (probablemente solo un error tipográfico).
bta
1
@AndrewS: Porque la función modificará el valor de la variable.
Jack
3
@Jack intsiempre está firmado.
jamesdlin
1
@jamesdlin: Mi error. Lo siento ... No sabía dónde leí eso.
Jack
1
Por alguna razón, la muestra genera un error con nota n format specifies disabled. ¿Cual es la razón?
Johnny_D
186

La mayoría de estas respuestas explican qué %n hace (que es no imprimir nada y escribir el número de caracteres impresos hasta ahora en una intvariable), pero hasta ahora nadie ha dado realmente un ejemplo de qué uso tiene. Acá hay uno:

int n;
printf("%s: %nFoo\n", "hello", &n);
printf("%*sBar\n", n, "");

imprimirá:

hello: Foo
       Bar

con Foo y Bar alineados. (Es trivial hacer eso sin usar %npara este ejemplo en particular, y en general uno siempre puede romper esa primera printfllamada:

int n = printf("%s: ", "hello");
printf("Foo\n");
printf("%*sBar\n", n, "");

Si la conveniencia ligeramente agregada vale la pena usar algo esotérico como %n(y posiblemente introducir errores) está abierta a debate).

jamesdlin
fuente
3
¡Oh, esta es una versión basada en caracteres para calcular el tamaño de píxel de la cadena en una fuente dada!
¿Podría explicar por qué se necesitan & n y * s. ¿Son ambos punteros?
Andrew S
9
@AndrewS &nes un puntero ( &es la dirección del operador); es necesario un puntero porque C es un valor de paso y, sin un puntero, printfno podría modificar el valor de n. El %*suso en la printfcadena de formato imprime un %sespecificador (en este caso la cadena vacía "") usando un ancho de campo de ncaracteres. Una explicación de los printfprincipios básicos está básicamente fuera del alcance de esta pregunta (y respuesta); Recomiendo leer la printfdocumentación o hacer su propia pregunta por separado sobre SO.
jamesdlin
3
Gracias por mostrar un caso de uso. No entiendo por qué la gente básicamente copia y pega el manual en SO y lo reformula a veces. Somos humanos y todo se hace por una razón que siempre debe explicarse en una respuesta. "No hace nada" es como decir "La palabra cool significa cool" - conocimiento casi inútil.
the_endian
1
@PSkocik Es enrevesado y propenso a errores sin agregar un nivel adicional de indirección.
jamesdlin
18

Realmente no he visto muchos usos prácticos del %nespecificador en el mundo real , pero recuerdo que se usó en vulnerabilidades de printf de la vieja escuela con un ataque de cadena de formato bastante tiempo.

Algo que fue asi

void authorizeUser( char * username, char * password){

    ...code here setting authorized to false...
    printf(username);

    if ( authorized ) {
         giveControl(username);
    }
}

donde un usuario malintencionado podría aprovechar el parámetro del nombre de usuario que se pasa a printf como la cadena de formato y usar una combinación de %d,%c o w / e para pasar por la pila de llamadas y luego modificar la variable autorizada a un valor verdadero.

Sí, es un uso esotérico, pero ¿siempre es útil saber cuándo escribir un demonio para evitar agujeros de seguridad? :RE

Xzhsh
fuente
1
Hay más razones que %npara evitar usar una cadena de entrada sin marcar como una printfcadena de formato.
Keith Thompson
13

Desde aquí vemos que almacena la cantidad de caracteres impresos hasta el momento.

n El argumento será un puntero a un número entero en el que se escribe el número de bytes escritos en la salida hasta ahora por esta llamada a una de las fprintf()funciones. No se convierte ningún argumento.

Un ejemplo de uso sería:

int n_chars = 0;
printf("Hello, World%n", &n_chars);

n_charsentonces tendría un valor de 12.

KLee1
fuente
10

Hasta ahora, todas las respuestas son sobre eso %n, pero no por qué alguien lo querría en primer lugar. Creo que es algo útil con sprintf/ snprintf, cuando es posible que deba dividir o modificar la cadena resultante, ya que el valor almacenado es un índice de matriz en la cadena resultante. Sin embargo, esta aplicación es mucho más útil, sscanfespecialmente porque las funciones en la scanffamilia no devuelven el número de caracteres procesados ​​sino el número de campos.

Otro uso realmente hackeo es obtener un pseudo-log10 gratis al mismo tiempo mientras imprime un número como parte de otra operación.

R .. GitHub DEJA DE AYUDAR AL HIELO
fuente
+1 por mencionar usos para %n, aunque ruego diferir sobre "todas las respuestas ...". = P
jamesdlin
1
Los malos te agradecen por tu uso de printf /% n, sprintf y sscanf;)
jww
66
@noloader: ¿Cómo es eso? El uso de %n tiene absolutamente cero peligro de vulnerabilidad para un atacante. La infamia fuera de lugar %nrealmente pertenece a la estúpida práctica de pasar una cadena de mensaje en lugar de una cadena de formato como argumento de formato. Esta situación, por supuesto, nunca surge cuando en %nrealidad es parte de una cadena de formato intencional que se está utilizando.
R .. GitHub DEJA DE AYUDAR AL HIELO
% n te permite escribir en la memoria. Creo que está asumiendo que el atacante no controla ese puntero (podría estar equivocado). Si el atacante controla el puntero (es solo otro parámetro para imprimir), él / ella podría realizar una escritura de 4 bytes. Si él / ella puede beneficiarse es una historia diferente.
jww
8
@noloader: Eso es cierto sobre cualquier uso de punteros. Nadie dice "chicos malos, gracias" por escribir *p = f();. ¿Por qué debería %n, que es solo otra forma de escribir un resultado al objeto señalado por un puntero, ser considerado "peligroso", en lugar de considerar al puntero como peligroso?
R .. GitHub DEJA DE AYUDAR AL HIELO
10

El argumento asociado con el %nserá tratado como un int*y se rellena con el número total de caracteres impresos en ese punto en el printf.

Evan
fuente
9

El otro día me encontré en una situación en la %nque resolvería muy bien mi problema. A diferencia de mi respuesta anterior , en este caso, no puedo idear una buena alternativa.

Tengo un control GUI que muestra un texto específico. Este control puede mostrar parte de ese texto en negrita (o en cursiva, o subrayado, etc.), y puedo especificar qué parte especificando índices de caracteres iniciales y finales.

En mi caso, estoy generando el texto para el control snprintf, y me gustaría que una de las sustituciones se ponga en negrita. Encontrar los índices iniciales y finales de esta sustitución no es trivial porque:

  • La cadena contiene múltiples sustituciones, y una de las sustituciones es texto arbitrario especificado por el usuario. Esto significa que hacer una búsqueda textual de la sustitución que me interesa es potencialmente ambigua.

  • La cadena de formato puede estar localizada y puede usar la $extensión POSIX para los especificadores de formato posicional. Por lo tanto, buscar en la cadena de formato original los propios especificadores de formato no es trivial.

  • El aspecto de localización también significa que no puedo dividir fácilmente la cadena de formato en varias llamadas a snprintf.

Por lo tanto, la forma más sencilla de encontrar los índices en torno a una sustitución particular sería hacer:

char buf[256];
int start;
int end;

snprintf(buf, sizeof buf,
         "blah blah %s %f yada yada %n%s%n yakety yak",
         someUserSpecifiedString,
         someFloat,
         &start, boldString, &end);
control->set_text(buf);
control->set_bold(start, end);
jamesdlin
fuente
Te daré +1 por el caso de uso. Pero va a fallar una auditoría, por lo que probablemente debería idear otra forma de marcar el comienzo y el final del texto en negrita. Parece que tres snprintfal verificar los valores de retorno funcionarán bien ya que snprintfdevuelve el número de caracteres escritos. Tal vez algo como: int begin = snprintf(..., "blah blah %s %f yada yada", ...);y int end = snprintf(..., "%s", ...);y luego la cola: snprintf(..., "blah blah");.
jww
3
@jww El problema con las snprintfllamadas múltiples es que las sustituciones pueden reorganizarse en otras configuraciones regionales, por lo que no se pueden dividir así.
jamesdlin
Gracias por el ejemplo Pero, ¿no podría, por ejemplo, escribir una secuencia de control de terminal para poner la salida en negrita justo antes del campo y luego escribir una secuencia después? Si no codifica las secuencias de control del terminal, también puede hacer que sean posicionales (reorderables).
PSkocik
1
@PSkocik Si está enviando a una terminal. Si está trabajando con, por ejemplo, un control de edición enriquecida de Win32, eso no ayudará a menos que desee volver y analizar las secuencias de control de terminal después. Eso también supone que desea respetar las secuencias de control de terminal en el resto del texto sustituido; Si no lo hace, entonces tendría que filtrar o escapar de ellos. No digo que sea imposible prescindir de él %n; Estoy afirmando que usar %nes más sencillo que las alternativas.
jamesdlin
1

No imprime nada. Se utiliza para determinar cuántos caracteres se imprimieron antes de que %naparecieran en la cadena de formato y enviarlos al int proporcionado:

#include <stdio.h>

int main(int argc, char* argv[])
{
    int resultOfNSpecifier = 0;
    _set_printf_count_output(1); /* Required in visual studio */
    printf("Some format string%n\n", &resultOfNSpecifier);
    printf("Count of chars before the %%n: %d\n", resultOfNSpecifier);
    return 0;
}

( Documentación para_set_printf_count_output )

Merlyn Morgan-Graham
fuente
0

Almacenará el valor del número de caracteres impresos hasta ahora en esa printf()función.

Ejemplo:

int a;
printf("Hello World %n \n", &a);
printf("Characters printed so far = %d",a);

La salida de este programa será

Hello World
Characters printed so far = 12
Sudhanshu Mishra
fuente
cuando trato de codificar me da: ¡Hola, Caracteres Mundiales impresos hasta ahora = 36 ,,,,, por qué 36?! Yo uso un GCC de 32 bits en una máquina con Windows.
Sina Karvandi
-6

% n es C99, no funciona con VC ++.

usuario411313
fuente
2
%nexistió en C89. No funciona con MSVC porque Microsoft lo deshabilitó de manera predeterminada por cuestiones de seguridad; _set_printf_count_outputprimero debe llamar para habilitarlo. (Ver la respuesta de Merlyn Morgan-Graham.)
jamesdlin
No, C89 no define esta característica / backdoorbug. Vea K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? ¿dónde está el etiquetador de URL para comentarios?
user411313
44
Estás simplemente equivocado. Se enumera claramente en la Tabla B-1 ( printfconversiones) del Apéndice B de K&R, segunda edición. (Página 244 de mi copia). O consulte la sección 7.9.6.1 (página 134) de la especificación ISO C90.
jamesdlin
Android también eliminó el especificador% n.
jww