Qual é o uso do especificador de formato% n em C?

125

Qual é o uso do %nespecificador de formato em C? Alguém poderia explicar com um exemplo?

Josh
fonte
25
O que aconteceu com a arte de ler o manual de multa?
Jens
8
Eu acho que a verdadeira questão é qual é o PONTO de uma opção como esta? por que alguém iria querer saber o valor dos números de caracteres impressos e muito menos escrever esse valor diretamente na memória. Era como se os desenvolvedores foram entediado e decidiu introduzir um bug no kernal
Chen Jia
É por isso que Bionic deixou de lado.
solidak 4/09/18
1
Na verdade, é uma pergunta válida e que os manuais finos provavelmente não responderão; descobriu-se que %nmarcas printfacidentalmente Turing completo e você pode, por exemplo implementar Brainfuck nele, ver github.com/HexHive/printbf e oilshell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer

Respostas:

147

Nada impresso. O argumento deve ser um ponteiro para um int assinado, onde o número de caracteres gravados até agora é armazenado.

#include <stdio.h>

int main()
{
  int val;

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

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

  return 0;

}

O código anterior imprime:

blah  blah
val = 5
Starkey
fonte
1
Você menciona que o argumento deve ser um ponteiro para um int assinado e, em seguida, usou um int não assinado no seu exemplo (provavelmente apenas um erro de digitação).
quer
1
@ Andrews: Porque a função irá modificar o valor da variável.
Jack
3
@Jack inté sempre assinado.
Jamesdlin
1
@ JamesJlin: Meu erro. Me desculpe .. eu não sabia onde li isso.
21814 Jack
1
Por alguma razão, a amostra gera erro com nota n format specifies disabled. Qual o motivo?
21414 Johnny_D
186

A maioria dessas respostas explica o que %n faz (que é imprimir nada e escrever o número de caracteres impressos até agora em uma intvariável), mas até agora ninguém realmente deu um exemplo do uso que tem. Aqui está um:

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

irá imprimir:

hello: Foo
       Bar

com Foo e Bar alinhados. (É trivial fazer isso sem usar %nesse exemplo em particular, e em geral sempre se pode interromper a primeira printfchamada:

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

Se vale a pena acrescentar a conveniência um pouco esotérica %n(e possivelmente a introdução de erros) está aberto a debate.)

jamesdlin
fonte
3
Oh meu Deus - esta é uma versão baseada em caracteres da computação do tamanho de pixel da string em uma determinada fonte!
Você poderia explicar por que & n e * s são necessários. Ambos são indicadores?
Andrew S
9
@AndrewS &né um ponteiro ( &é o endereço do operador); um ponteiro é necessário porque C é pass-by-value e, sem um ponteiro, printfnão pode modificar o valor de n. O %*suso na printfstring de formato imprime um %sespecificador (neste caso, a string vazia "") usando uma largura de campo de ncaracteres. Uma explicação dos printfprincípios básicos está basicamente fora do escopo desta pergunta (e resposta); Eu recomendo ler a printfdocumentação ou fazer sua própria pergunta separada no SO.
jamesdlin
3
Obrigado por mostrar um caso de uso. Não entendo por que as pessoas basicamente copiam e colam o manual no SO e reformulam-no algumas vezes. Somos humanos e tudo é feito por uma razão que sempre deve ser explicada em uma resposta. "Não faz nada" é como dizer "A palavra legal significa legal" - conhecimento quase inútil.
precisa saber é o seguinte
1
@PSkocik É complicado e propenso a erros sem adicionar um nível extra de indireção.
Jamesdlin #
18

Eu realmente não vi muitos usos práticos do %nespecificador no mundo real , mas lembro que ele foi usado em vulnerabilidades de impressão oldschool com um ataque de string de formato há algum tempo.

Algo que foi assim

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

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

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

onde um usuário mal-intencionado pode tirar proveito do parâmetro de nome de usuário passado para printf como a string de formato e usar uma combinação de %d,%c ou w / e, para percorrer a pilha de chamadas e modificar a variável autorizada para um valor verdadeiro.

Sim, é um uso esotérico, mas sempre útil saber ao escrever um daemon para evitar falhas de segurança? : D

Xzhsh
fonte
1
Há mais motivos %npara evitar o uso de uma sequência de entrada não marcada como uma printfsequência de formato.
Keith Thompson
13

A partir daqui , vemos que ele armazena o número de caracteres impressos até o momento.

n O argumento deve ser um ponteiro para um número inteiro no qual está escrito o número de bytes gravados na saída até agora por esta chamada para uma das fprintf()funções. Nenhum argumento é convertido.

Um exemplo de uso seria:

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

n_charsteria então um valor de 12.

KLee1
fonte
10

Até agora, todas as respostas são sobre isso %n, mas não por que alguém iria querer isso em primeiro lugar. Acho que é um pouco útil com sprintf/ snprintf, quando você pode precisar posteriormente quebrar ou modificar a sequência resultante, pois o valor armazenado é um índice de matriz na sequência resultante. No entanto, esse aplicativo é muito mais útil, sscanfprincipalmente porque as funções da scanffamília não retornam o número de caracteres processados, mas o número de campos.

Outro uso realmente hackeado é obter um pseudo-log10 gratuitamente ao mesmo tempo, enquanto imprime um número como parte de outra operação.

R .. GitHub PARE DE AJUDAR O GELO
fonte
+1 por mencionar usos para %n, embora eu implore para diferir sobre "todas as respostas ...". = P
jamesdlin
1
Os bandidos agradecem pelo uso de printf /% n, sprintf e sscanf;)
jww
6
@noloader: Como assim? O uso de %n tem absolutamente zero risco de vulnerabilidade a um invasor. A infâmia incorreta de %nrealmente pertence à prática estúpida de passar uma sequência de mensagens em vez de uma sequência de formato como argumento de formato. É claro que essa situação nunca surge quando %nna verdade faz parte de uma string de formato intencional que está sendo usada.
R .. GitHub Pare de ajudar o gelo
% n permite gravar na memória. Eu acho que você está assumindo que o atacante não controla esse ponteiro (eu posso estar errado). Se o atacante controlar o ponteiro (é apenas outro parâmetro para imprimir), ele poderá executar uma gravação de 4 bytes. Se ele / ela pode lucrar é uma história diferente.
JWW
8
@ noloader: Isso é verdade sobre qualquer uso de ponteiros. Ninguém diz "bandidos obrigado" por escrever *p = f();. Por que %n, que é apenas outra maneira de escrever um resultado para o objeto apontado por um ponteiro, ser considerado "perigoso", em vez de considerar o ponteiro em si perigoso?
R .. GitHub Pare de ajudar o gelo
10

O argumento associado ao %nserá tratado como int*e é preenchido com o número total de caracteres impressos nesse ponto no printf.

Evan
fonte
9

Outro dia, me vi numa situação em %nque resolveria bem meu problema. Ao contrário da minha resposta anterior , neste caso, não consigo conceber uma boa alternativa.

Eu tenho um controle GUI que exibe algum texto especificado. Esse controle pode exibir parte desse texto em negrito (ou em itálico ou sublinhado etc.), e posso especificar qual parte especificando os índices de caracteres inicial e final.

No meu caso, estou gerando o texto para o controle com snprintfe gostaria que uma das substituições estivesse em negrito. A localização dos índices inicial e final dessa substituição não é trivial porque:

  • A cadeia contém várias substituições e uma das substituições é um texto arbitrário especificado pelo usuário. Isso significa que fazer uma pesquisa textual pela substituição com a qual me preocupo é potencialmente ambíguo.

  • A cadeia de formato pode estar localizada e pode usar a $extensão POSIX para especificadores de formato posicional. Portanto, pesquisar a sequência de formato original pelos próprios especificadores de formato não é trivial.

  • O aspecto da localização também significa que não posso dividir facilmente a sequência de formatação em várias chamadas para snprintf.

Portanto, a maneira mais direta de encontrar os índices em torno de uma substituição específica seria:

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
fonte
Darei +1 para o caso de uso. Mas como você falhará em uma auditoria, provavelmente deverá criar outra maneira de marcar o início e o fim do texto em negrito. Parece que três snprintfao verificar os valores de retorno funcionará bem, pois snprintfretorna o número de caracteres gravados. Talvez algo como: int begin = snprintf(..., "blah blah %s %f yada yada", ...);e int end = snprintf(..., "%s", ...);em seguida, a cauda: snprintf(..., "blah blah");.
JWW
3
@jww O problema com várias snprintfchamadas é que as substituições podem ser reorganizadas em outros locais, para que não possam ser divididas dessa maneira.
Jamesdlin #
Obrigado pelo exemplo. Mas você não poderia escrever uma sequência de controle de terminal para deixar a saída em negrito logo antes do campo e depois escrever uma sequência depois? Se você não codificar as seqüências de controle do terminal, poderá torná-las posicionais (reordenadas) também.
PSKocik
1
@PSkocik Se você estiver enviando para um terminal. Se você estiver trabalhando com, digamos, um controle de edição avançada do Win32, isso não ajudará, a menos que você queira voltar e analisar as seqüências de controle do terminal posteriormente. Isso também pressupõe que você deseja honrar as seqüências de controle terminal no restante do texto substituído; caso contrário, você teria que filtrar ou escapar deles. Não estou dizendo que é impossível ficar sem %n; Estou afirmando que o uso %né mais direto do que as alternativas.
jamesdlin
1

Não imprime nada. É usado para descobrir quantos caracteres foram impressos antes de %naparecerem na cadeia de formatação e enviar isso para o int fornecido:

#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;
}

( Documentação para_set_printf_count_output )

Merlyn Morgan-Graham
fonte
0

Ele armazenará o valor do número de caracteres impressos até agora nessa printf()função.

Exemplo:

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

A saída deste programa será

Hello World
Characters printed so far = 12
Sudhanshu Mishra
fonte
quando tento codificar, dá-me: Olá, mundo caracteres impressos até agora = 36 ,,,,, por que 36 ?! Eu uso um GCC de 32 bits em uma máquina Windows.
Sina Karvandi
-6

% n é C99, não funciona com VC ++.

user411313
fonte
2
%nexistia em C89. Ele não funciona com o MSVC porque a Microsoft o desativou por padrão por questões de segurança; você deve ligar _set_printf_count_outputprimeiro para habilitá-lo. (Resposta See Merlyn do Morgan-Graham.)
jamesdlin
Não, o C89 não define esse recurso / backdoorbug. Consulte K&R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? onde está o marcador de URL para comentários?
user411313
4
Você está simplesmente errado. Ele está listado claramente na Tabela B-1 ( printfconversões) do Apêndice B do K&R, 2ª edição. (Página 244 da minha cópia.) Ou consulte a seção 7.9.6.1 (página 134) da especificação ISO C90.
jamesdlin
O Android também removeu o especificador% n.
JWW