Em um livro que estou lendo, está escrito que printf
com um único argumento (sem especificadores de conversão) está obsoleto. Recomenda substituir
printf("Hello World!");
com
puts("Hello World!");
ou
printf("%s", "Hello World!");
Alguém pode me dizer por que printf("Hello World!");
está errado? Está escrito no livro que contém vulnerabilidades. Quais são essas vulnerabilidades?
printf("Hello World!")
é o mesmo que . anexa a . Em vez disso, compare computs("Hello World!")
puts()
'\n'
printf("abc")
fputs("abc", stdout)
printf
esteja obsoleto da mesma forma que, por exemplo,gets
está obsoleto no C99, então você pode considerar a edição de sua pergunta para ser mais preciso.Respostas:
printf("Hello World!");
IMHO não é vulnerável, mas considere o seguinte:Se
str
acontecer de apontar para uma string contendo%s
especificadores de formato, seu programa exibirá um comportamento indefinido (principalmente uma falha), enquantoputs(str)
apenas exibirá a string como está.Exemplo:
fonte
puts
provavelmente será mais rápido.puts
é "presumivelmente" mais rápido, e esse é provavelmente outro motivo pelo qual as pessoas o recomendam, mas não é realmente mais rápido. Acabei de imprimir"Hello, world!"
1.000.000 de vezes, dos dois lados. Comprintf
ele demorou 0,92 segundos. Computs
ele demorou 0,93 segundos. Há coisas com que se preocupar quando se trata de eficiência, mas oprintf
vs.puts
não é uma delas.puts
é mais rápido" é falsa, ainda é falsa.puts
por esse motivo. (Mas se você quisesse discutir sobre isso: eu ficaria surpreso se você pudesse encontrar qualquer compilador moderno para qualquer máquina moderna ondeputs
seja significativamente mais rápido do queprintf
em qualquer circunstância.)printf("Hello world");
está bem e não tem vulnerabilidade de segurança.
O problema está em:
onde
p
é um ponteiro para uma entrada controlada pelo usuário. É propenso a ataques de strings de formato : o usuário pode inserir especificações de conversão para assumir o controle do programa, por exemplo,%x
para despejar a memória ou%n
sobrescrever a memória.Observe que
puts("Hello world")
não é equivalente em comportamento a,printf("Hello world")
mas aprintf("Hello world\n")
. Os compiladores geralmente são inteligentes o suficiente para otimizar a última chamada para substituí-laputs
.fonte
printf(p,x)
que seria igualmente problemático se o usuário tivesse controle sobrep
. Portanto, o problema não é o uso deprintf
com apenas um argumento, mas sim com uma string de formato controlada pelo usuário.printf(p)
, é porque não percebem que é uma string de formato, elas apenas pensam que estão imprimindo um literal.Além das outras respostas,
printf("Hello world! I am 50% happy today")
é um bug fácil de fazer, potencialmente causando todos os tipos de problemas de memória desagradáveis (é UB!).É apenas mais simples, fácil e mais robusto "exigir" que os programadores sejam absolutamente claros quando desejam uma string literal e nada mais .
E é isso que
printf("%s", "Hello world! I am 50% happy today")
você pega. É totalmente à prova de falhas.(Steve,
printf("He has %d cherries\n", ncherries)
é claro que não é absolutamente a mesma coisa; neste caso, o programador não está na mentalidade de "string literal"; ela está na mentalidade de "format string".)fonte
printf
" é exatamente como dizer "sempre escrevaif(NULL == p)
. Essas regras podem ser úteis para alguns programadores, mas não para todos. E em ambos os casos (printf
formatos incompatíveis e condicionais Yoda), os compiladores modernos avisam sobre erros de qualquer maneira, portanto, as regras artificiais são ainda menos importantes.printf("%s", "hello")
vai ser mais lento do queprintf("hello")
, então há uma desvantagem. Um pequeno, porque IO é quase sempre muito mais lento do que uma formatação simples, mas é uma desvantagem.gcc -Wall -W -Werror
irá prevenir consequências ruins de tais erros.Vou apenas adicionar algumas informações sobre a parte da vulnerabilidade aqui.
Diz-se que é vulnerável devido à vulnerabilidade do formato de string printf. Em seu exemplo, onde a string é codificada, é inofensiva (mesmo se a codificação de strings como esta nunca for totalmente recomendada). Mas especificar os tipos de parâmetro é um bom hábito a se tomar. Veja este exemplo:
Se alguém colocar o caractere da string de formato em seu printf ao invés de uma string regular (digamos, se você quiser imprimir o programa stdin), printf pegará tudo o que puder na pilha.
Foi (e ainda é) muito usado para explorar programas para explorar pilhas para acessar informações ocultas ou contornar a autenticação, por exemplo.
Exemplo (C):
se eu colocar como entrada deste programa
"%08x %08x %08x %08x %08x\n"
Isso instrui a função printf a recuperar cinco parâmetros da pilha e exibi-los como números hexadecimais preenchidos com 8 dígitos. Portanto, uma possível saída pode ser:
Veja isto para uma explicação mais completa e outros exemplos.
fonte
Chamar
printf
com strings de formato literal é seguro e eficiente, e existem ferramentas para avisá-lo automaticamente se a invocação deprintf
com strings de formato fornecidas pelo usuário não for segura.Os ataques mais severos em
printf
tiram vantagem do%n
especificador de formato. Em contraste com todos os outros especificadores de formato, por exemplo%d
,%n
realmente grava um valor em um endereço de memória fornecido em um dos argumentos de formato. Isso significa que um invasor pode sobrescrever a memória e, portanto, potencialmente assumir o controle de seu programa. A Wikipedia fornece mais detalhes.Se você chamar
printf
com uma string de formato literal, um invasor não poderá%n
inserir um em sua string de formato e, portanto, você estará seguro. Na verdade, o gcc mudará sua chamada paraprintf
em uma chamada paraputs
, então literalmente não há nenhuma diferença (teste executandogcc -O3 -S
).Se você chamar
printf
com uma string de formato fornecida pelo usuário, um invasor pode potencialmente%n
inserir um em sua string de formato e assumir o controle de seu programa. Seu compilador geralmente irá avisá-lo de que o dele não é seguro, veja-Wformat-security
. Existem também ferramentas mais avançadas que garantem que uma invocação deprintf
é segura, mesmo com strings de formato fornecidas pelo usuário, e podem até verificar se você passou o número e o tipo correto de argumentos paraprintf
. Por exemplo, para Java, existe o Google's Error Prone e o Checker Framework .fonte
Este é um conselho equivocado. Sim, se você tiver uma string de tempo de execução para imprimir,
é muito perigoso, e você deve sempre usar
em vez disso, porque em geral você nunca sabe se
str
pode conter um%
sinal. No entanto, se você tiver uma string constante de tempo de compilação , não há nada de errado com(Entre outras coisas, esse é o programa C mais clássico de todos os tempos, literalmente do livro de programação C do Gênesis. Portanto, qualquer pessoa que rejeite esse uso está sendo um tanto herética, e eu ficaria um pouco ofendido!)
fonte
because printf's first argument is always a constant string
Não tenho certeza do que você quer dizer com isso."He has %d cherries\n"
é uma string constante, o que significa que é uma constante de tempo de compilação. Mas, para ser justo, o conselho do autor não era "não passe strings constantes comoprintf
o primeiro argumento", era "não passe strings sem%
comoprintf
primeiro argumento".literally from the C programming book of Genesis. Anyone deprecating that usage is being quite offensively heretical
- você realmente não leu K&R nos últimos anos. Há uma tonelada de conselhos e estilos de codificação que não apenas foram descontinuados, mas são apenas práticas inadequadas atualmente.int
" vem à mente.)Um aspecto bastante desagradável de
printf
é que mesmo em plataformas onde as leituras de memória perdida só podem causar danos limitados (e aceitáveis), um dos caracteres de formatação%n
, faz com que o próximo argumento seja interpretado como um ponteiro para um inteiro gravável e faz com que o número de caracteres produzidos até agora para serem armazenados na variável identificada. Eu nunca usei esse recurso, e às vezes eu uso métodos leves do estilo printf que escrevi para incluir apenas os recursos que eu realmente uso (e não incluo aquele ou algo semelhante), mas alimentando strings de funções de printf padrão recebidas de fontes não confiáveis podem expor vulnerabilidades de segurança além da capacidade de ler armazenamento arbitrário.fonte
Já que ninguém mencionou, gostaria de acrescentar uma nota sobre o desempenho deles.
Em circunstâncias normais, supondo que nenhuma otimização do compilador seja usada (ou seja,
printf()
realmente chamadasprintf()
e nãofputs()
), eu esperaria umprintf()
desempenho menos eficiente, especialmente para strings longas. Isso ocorre porqueprintf()
é necessário analisar a string para verificar se há algum especificador de conversão.Para confirmar isso, fiz alguns testes. O teste é realizado no Ubuntu 14.04, com gcc 4.8.4. Minha máquina usa uma CPU Intel i5. O programa que está sendo testado é o seguinte:
Ambos são compilados com
gcc -Wall -O0
. O tempo é medido usandotime ./a.out > /dev/null
. O seguinte é o resultado de uma execução típica (eu os executei cinco vezes, todos os resultados estão dentro de 0,002 segundos).Para a
printf()
variante:Para a
fputs()
variante:Este efeito é amplificado se você tiver uma corda muito longa.
Para a
printf()
variante (executado três vezes, real mais / menos 1,5s):Para a
fputs()
variante (executado três vezes, real mais / menos 0,2s):Nota: Depois de inspecionar o assembly gerado pelo gcc, percebi que o gcc otimiza a
fputs()
chamada para umafwrite()
chamada, mesmo com-O0
. (Aprintf()
chamada permanece inalterada.) Não tenho certeza se isso invalidará meu teste, pois o compilador calcula o comprimento da stringfwrite()
no momento da compilação.fonte
fputs()
costuma ser usado com constantes de string e essa oportunidade de otimização é parte do ponto que você queria fazer. Dito isso, adicionar uma execução de teste com uma string gerada dinamicamente comfputs()
efprintf()
seria um bom ponto de dados suplementar ./dev/null
meio que torna isso um brinquedo, pois geralmente ao gerar uma saída formatada, seu objetivo é que a saída vá para algum lugar, não seja descartada. Depois de adicionar o tempo "na verdade, não descartando os dados", como eles se comparam?compila automaticamente para o equivalente
você pode verificá-lo desmontando seu executável:
usando
levará a problemas de segurança, nunca use o printf dessa forma!
então seu livro está realmente correto, usar printf com uma variável está obsoleto, mas você ainda pode usar printf ("minha string \ n") porque ele se tornará automaticamente puts
fonte
A compiles to B
, mas na realidade você quer dizerA and B compile to C
.Para gcc, é possível habilitar avisos específicos para verificação
printf()
escanf()
.A documentação do gcc afirma:
O
-Wformat
que está habilitado na-Wall
opção não habilita vários avisos especiais que ajudam a encontrar estes casos:-Wformat-nonliteral
avisará se você não passar uma string litteral como especificador de formato.-Wformat-security
avisará se você passar uma string que pode conter uma construção perigosa. É um subconjunto de-Wformat-nonliteral
.Tenho que admitir que a ativação
-Wformat-security
revelou vários bugs que tínhamos em nossa base de código (módulo de registro, módulo de tratamento de erros, módulo de saída xml, todos tinham algumas funções que poderiam fazer coisas indefinidas se tivessem sido chamadas com% caracteres em seus parâmetros. nossa base de código agora tem cerca de 20 anos e mesmo que estivéssemos cientes desse tipo de problema, ficamos extremamente surpresos quando habilitamos esses avisos de quantos desses bugs ainda estavam na base de código).fonte
Além das outras respostas bem explicadas com quaisquer questões secundárias abordadas, gostaria de dar uma resposta precisa e concisa à pergunta fornecida.
Uma
printf
chamada de função com um único argumento em geral não é obsoleta e também não tem vulnerabilidades quando usada corretamente, pois você sempre deve codificar.C Usuários em todo o mundo, de iniciante a especialista em status, usam
printf
essa forma para fornecer uma frase de texto simples como saída para o console.Além disso, Alguém tem que distinguir se este único argumento é uma string literal ou um ponteiro para uma string, que é válido, mas normalmente não é usado. Para o último, é claro, podem ocorrer saídas inconvenientes ou qualquer tipo de comportamento indefinido , quando o ponteiro não está definido corretamente para apontar para uma string válida, mas essas coisas também podem ocorrer se os especificadores de formato não corresponderem aos respectivos argumentos, fornecendo vários argumentos.
Obviamente, também não é correto e apropriado que a string, fornecida como um e único argumento, tenha qualquer formato ou especificadores de conversão, uma vez que não haverá conversão.
Dito isso, fornecer um literal de string simples
"Hello World!"
como único argumento sem nenhum especificador de formato dentro dessa string, como você forneceu na pergunta:não é preterido ou " má prática " nem tem vulnerabilidades.
Na verdade, muitos programadores C começam e começaram a aprender e usar C ou mesmo linguagens de programação em geral com aquele programa HelloWorld e esta
printf
instrução como os primeiros de seu tipo.Eles não seriam isso se fossem descontinuados.
Bem, então eu colocaria o foco no livro ou no próprio autor. Se um autor está realmente fazendo essas afirmações , na minha opinião, incorretas e até mesmo ensinando isso sem explicar explicitamente por que está fazendo isso (se essas afirmações forem realmente literalmente equivalentes fornecidas naquele livro), eu consideraria um livro ruim . Um bom livro, ao contrário disso, deve explicar por que evitar certos tipos de métodos ou funções de programação.
De acordo com o que eu disse acima, usar
printf
com apenas um argumento (uma string literal) e sem quaisquer especificadores de formato não é em nenhum caso descontinuado ou considerado como "má prática" .Você deve perguntar ao autor o que ele quis dizer com isso ou, melhor ainda, que ele esclareça ou corrija a seção relativa para a próxima edição ou impressões em geral.
fonte
printf("Hello World!");
é equivalente a qualquer maneira, o que diz algo sobre o autor da recomendação.puts("Hello World!");