1 pontos por GN⁺ 3 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • No Linux 7.2, os usos internos da API strncpy desapareceram do kernel, concluindo a remoção final dessa interface de cópia de strings que já estava marcada para descontinuação havia muito tempo
  • strncpy() copia a quantidade especificada de bytes, mas o comportamento de terminação NUL não é intuitivo, o que por anos fez dela uma fonte de bugs no kernel
  • Sua característica de preencher o buffer de destino com zeros desnecessariamente também gerava problemas de desempenho, e foram necessários cerca de 6 anos e 362 commits para eliminar isso
  • No merge de sexta-feira, além do corpo principal da API, também foi removida a última implementação específica por arquitetura para per-CPU
  • O código do kernel agora precisa escolher funções alternativas conforme o uso, como strscpy(), strscpy_pad(), strtomem_pad(), memcpy_and_pad(), memcpy()

strncpy desaparece no Linux 7.2

  • O Linux 7.2 concluiu a remoção da API strncpy, que no kernel já estava marcada para descontinuação havia muito tempo
  • Após 6 anos de trabalho de limpeza, não resta mais código interno do kernel usando a interface strncpy
  • A mudança não foi apenas uma troca simples de função, mas quase uma eliminação de práticas antigas de cópia de strings em todo o kernel

O tamanho do trabalho até a remoção

  • A remoção de strncpy exigiu cerca de 362 commits
  • O trabalho avançou de forma gradual, eliminando aos poucos os usos de strncpy dentro do kernel
  • No Linux 7.2, esse processo de limpeza chegou ao fim

Por que strncpy era um problema no kernel

  • strncpy era vista há anos no kernel Linux como uma fonte recorrente de bugs
  • Dois comportamentos em especial eram problemáticos
    • O significado e o comportamento da terminação NUL não são intuitivos, o que facilita erros de uso
    • O preenchimento redundante com zeros no buffer de destino gerava custo de desempenho desnecessário

O merge que fez a remoção

  • O merge feito na sexta-feira removeu a API strncpy
  • No mesmo merge, também desapareceu a última implementação de strncpy específica por arquitetura para per-CPU

APIs alternativas para usar no código do kernel

  • Em vez de strncpy, agora é preciso escolher a função adequada ao destino da cópia e à condição de terminação
    • strscpy(): para destinos com terminação NUL
    • strscpy_pad(): para destinos com terminação NUL quando for necessário preenchimento com zeros
    • strtomem_pad(): para campos de largura fixa sem terminação NUL
    • memcpy_and_pad(): para cópia limitada com padding explícito
    • memcpy(): para cópia de memória quando o comprimento é conhecido

1 comentários

 
GN⁺ 3 시간 전
Comentários do Hacker News
  • Antigamente eu costumava tirar sarro dos desenvolvedores do kernel Linux, supostamente os melhores programadores de C do mundo, por não saberem criar tipos como stringbuffer ou stringview, mas dá para entender até certo ponto, porque naquela época não existia o consenso atual sobre esse tema
    Quem já tinha enxergado a direção certa era Dennis Ritchie, que em 1990 propôs um tipo de fat pointer para C. Teria sido uma adição perfeita se tivesse entrado no C99, e o mundo poderia ter sido bem diferente se o comitê tivesse incluído isso
    Em 2007 houve uma segunda chance com o texto “C's greatest mistake”, de Walter Bright, que explicava de forma mais clara slices/stringview, essencialmente a mesma ideia do Ritchie, mas também não entrou no C11. Já chegamos ao C23 e ainda não temos isso; em vez disso, ganhamos _Generic e VLA, então parece aquela situação de “vamos comemorar muito”

    • O texto de 2007 do Walter Bright está aqui: https://digitalmars.com/articles/C-biggest-mistake.html
      Pesquisando, também encontrei um post no Reddit sobre o mesmo tema, e a discussão de bike-shedding foi engraçada: https://www.reddit.com/r/C_Programming/comments/90uq7c/cs_bi...
      Fico curioso sobre por que o comportamento de arrays em C decaírem para ponteiros foi projetado assim. Há explicações de que a intenção era permitir compilar código B como C com mudanças mínimas, e em B a declaração de array na prática definia um ponteiro e um array, inicializando esse ponteiro para apontar para o primeiro elemento do array
    • VLA foi rebaixado a recurso opcional no C11, e acho isso uma coisa boa
      O problema maior hoje é que a biblioteca padrão de C continua presa à era K&R, e nem mesmo recursos da linguagem adicionados no C99, como parâmetros e valores de retorno com struct, foram refletidos nas APIs da biblioteca padrão. Já melhoraria bastante se a biblioteca padrão tivesse structs de intervalo como pares ponteiro/tamanho, junto com novas funções de string ou versões atualizadas das existentes que usassem isso
    • Link para a proposta do Ritchie: https://web.archive.org/web/20150611114358/https://www.bell-...
    • Esse é o padrão que mais me irrita em trabalho em equipe. Existem as soluções A, B e C, cada uma com vantagens e desvantagens, a equipe passa 2 semanas discutindo e, no fim, não escolhe nada
    • Isso só mostra onde estão as prioridades do WG14
  • Dizem que o strncpy dentro do kernel Linux foi por anos uma “fonte persistente de bugs”, por causa da semântica contraintuitiva, do tratamento de terminação NUL e do problema de desempenho de preencher o destino com zeros desnecessariamente
    Sempre que me pediam para revisar código em C, eu procurava por strncpy e quase sempre encontrava algum bug ali

  • Há coisas que me incomodam há 40 anos. Strings terminadas em NUL, e agora também strings que não são UTF-8 em entrada e saída
    O costume de tratar fim de linha como LF, CR ou CRLF também, assim como separar campos com pipe ou vírgula. Se tivessem usado caracteres ASCII não ambíguos como GS, FS e RS, a codificação/decodificação de fim de linha seria apenas um problema de E/S, e HT/VT/CR/LF/FF poderiam ter permanecido literalmente como códigos relacionados à saída

    • Já trabalhei em um projeto que convertia dados enquadrados com caracteres de separação de campo/registro do ASCII, e era realmente muito fácil de lidar
      Ficou muito mais simples porque desapareceu toda a sujeira de tratamento de escape que aparece em dados separados por vírgula
    • O Unicode agora tem ainda mais opções. Há o NL Next line, que parece vindo do EBCDIC, além do LS Line separator e do PS Paragraph separator, criados pelo Unicode
      O padrão Unicode diz que não só CR, LF, CRLF e esses caracteres, mas também tabulação vertical e form feed devem ser tratados como separadores de linha
    • UTF-8 funciona perfeitamente bem em entrada e saída padrão. Claro, isso vale se você não estiver falando do Windows, que no texto internacional ainda parece parado no começo dos anos 90
      Fins de linha como LF, CR e CRLF também são convenções de sistema operacional, e é melhor que linguagens de programação não tentem “adivinhar” o fim de linha correto. Isso cria mais problemas do que resolve e, de novo, em geral é um problema específico do Windows que a Microsoft deveria trazer para o século atual
    • LF é o que mais faz sentido, mas em arquivo de texto tanto faz. O problema é que CSV não é texto
      Da última vez que tive que lidar com arquivo CSV em bash, converti internamente para RS e FS e processei assim
    • Acho que é só usar UTF-8 em todo lugar
  • Em vez de strncpy, o código do kernel Linux recomenda usar strscpy() para destinos terminados em NUL, strscpy_pad() para destinos terminados em NUL que precisam de padding com zero, strtomem_pad() para campos de largura fixa sem terminação NUL, memcpy_and_pad() para cópia limitada com padding explícito e memcpy() para cópia de memória com comprimento conhecido
    Isso parece um pesadelo, e não sei se precisa ser tão complicado assim

    • O motivo é desempenho. Uma função genérica e segura que lide com tudo isso inevitavelmente fica mais lenta por causa de ramificações internas, e a escolha da função também expressa a intenção do desenvolvedor
      Acho melhor quando, ao ler o código, a intenção do desenvolvedor já fica clara só pela função escolhida
    • Usar strncpy corretamente sempre foi complicado mesmo
    • Pelo menos podiam ter dado nomes um pouco melhores
  • É nesse tipo de trabalho repetitivo e entediante que o verdadeiro trabalho da engenharia de sistemas acontece
    Projetos grandes de infraestrutura como esse, que tornam o kernel Linux mais confiável enquanto o mantêm utilizável em produção durante todo o processo, se movem em escala de décadas, não de meses

    • Entendo por que isso acaba virando algo na escala de décadas. A cauda longa de usuários e dependências é realmente enorme
      Mas não sei se dá para produzir progresso significativo de longo prazo nesse ritmo. Não é exatamente uma reclamação, é mais um paradoxo da infraestrutura central
  • É um trabalho impressionante e que dá humildade. É surpreendente que tanta gente tenha contribuído
    “Recursos novos e legais” tendem a receber reconhecimento com mais facilidade, mas em algo tão fundamental quanto o kernel, remover recursos ruins pode ser ainda mais importante
    Quando, daqui a 50 anos, as pessoas esquecerem como ler código-fonte e os restos de Claude/Codex forem se acumulando silenciosamente enquanto queimam a maior parte da energia da Terra, parece que esse tipo de trabalho vai sobreviver como uma lenda da “era de fundação”

    • Isso me lembra A Deepness in the Sky, de Vernor Vinge. Nele, uma pessoa faz manutenção de uma nave espacial por meio de arqueologia de software
      E também é a única pessoa que sabe o que é a Unix epoch
    • Não acho que, daqui a 50 anos, todo mundo vai ter esquecido como entender código-fonte. O desejo humano de saber como as coisas funcionam ainda vai existir
    • Acho que o código salada gerado por IA vai se tornar intratável muito antes disso
  • Acho que as strings terminadas em 0 são o maior erro da história da computação. Strings no estilo Pascal eram muito mais seguras

    • Também existe um meio-termo, como o BSTR adotado pelo Visual Basic e depois pelo COM
      Ainda é um ponteiro para um array de caracteres terminado em 0, mas há um campo de comprimento logo antes do primeiro byte apontado pelo ponteiro. Partindo da premissa de que não há NUL embutido, também é compatível com strings em C, e funções do tipo BSTR podem aproveitar o valor do comprimento
    • Concordo até certo ponto, mas provavelmente teria havido discussão sobre o tipo de dado do campo de tamanho. Se não fosse de comprimento variável, isso teria sido ainda mais forte; se fosse variável, surgiriam outros problemas
      Por um tempo, até 16 bits podem ter parecido exagerados, e hoje 32 bits podem parecer pequenos demais. O C, que é uma linguagem de “tipagem forte”, na prática é bastante frouxo justamente em pontos importantes
    • Strings terminadas em 0 foram a base de uma quantidade enorme de software útil. Chamar isso de o maior erro da computação é um pouco de exagero
      Não escrevo código relacionado a Pascal há mais de 30 anos, mas tenho uma lembrança vaga de que, mesmo na época, eu achava o sistema de strings complicado demais de usar
    • 255 caracteres não deveriam ser suficientes para todo mundo?
    • Tão ruim quanto linhas terminadas por quebra de linha
  • Há dor e retrabalho demais por não existir nem um único tipo de string

    • Mais precisamente, não é por não existir um tipo de string, e sim pela dor e pelo retrabalho causados por contornar o fato de que C não tem um tipo de string
    • Que forma de introduzir tipagem forte aqui seria possível? Imagino que seria preciso um grande refactor para que o código em torno de strncpy também passasse a usar esse tipo e essas funções
  • Fico curioso sobre o que havia de tão difícil em reescrever os usos de strncpy a ponto de isso ter levado 6 anos
    Gostaria de saber se era porque o uso estava realmente espalhado, se foi um trabalho de longo prazo feito só quando surgia a chance de mexer no mesmo arquivo, ou se houve alguma outra dificuldade

  • Já lidei com código em apps Win32 que usava strings com preenchimento por espaços. A string de destino era preenchida com espaços, mas o último byte ainda era um nulo
    Para operações como comprimento e cópia, era preciso usar versões específicas das funções de string. Não sei por que faziam isso, mas a base de código era tão antiga que pode ter vindo do comportamento de structs em Pascal

    • Pode ser que viesse de strings de campos char em bancos de dados SQL. Diferente de varchar, campos char são preenchidos com espaços
    • Acho que a raiz desse comportamento não é Pascal, e sim COBOL
    • Também pode ter sido uma tentativa de evitar realocação quando o tamanho da string mudava, ou algo relacionado ao alinhamento de linhas de cache da CPU