Atualmente, estou trabalhando em uma biblioteca escrita em C. Muitas funções dessa biblioteca esperam uma string como char*
ou const char*
em seus argumentos. Comecei com essas funções sempre esperando o comprimento da string como um size_t
para que a terminação nula não fosse necessária. No entanto, ao escrever testes, isso resultou no uso frequente de strlen()
, assim:
const char* string = "Ugh, strlen is tedious";
libFunction(string, strlen(string));
Confiar no usuário para transmitir seqüências finalizadas corretamente levaria a um código menos seguro, mas mais conciso e (na minha opinião) legível:
libFunction("I hope there's a null-terminator there!");
Então, qual é a prática sensata aqui? Torne a API mais complicada de usar, mas force o usuário a pensar em sua entrada ou a documentar o requisito para uma sequência terminada em nulo e confiar no chamador?
CreateFile
recebe umLPTCSTR lpFileName
parâmetro como entrada. Nenhum comprimento da sequência é esperado do chamador. De fato, o uso de cadeias terminadas em NUL é tão arraigado que a documentação nem menciona que o nome do arquivo deve ser terminado por NUL (mas é claro que deve ser).LPSTR
tipo diz que as seqüências de caracteres podem ter terminação NUL e, se não , isso será indicado na especificação associada. Portanto, a menos que seja indicado de outra forma, espera-se que essas seqüências de caracteres no Win32 sejam encerradas por NUL.StringCbCat
por exemplo, apenas o destino tem um buffer máximo, o que faz sentido. A fonte ainda é uma sequência C comum com terminação NUL. Talvez você possa melhorar sua resposta esclarecendo a diferença entre um parâmetro de entrada e um parâmetro de saída . Os parâmetros de saída devem sempre ter um tamanho máximo de buffer; os parâmetros de entrada geralmente são terminados em NUL (existem exceções, mas raras na minha experiência).Em C, o idioma é que as cadeias de caracteres são terminadas com NUL, por isso faz sentido respeitar a prática comum - é relativamente improvável que os usuários da biblioteca tenham cadeias sem terminação NUL (uma vez que precisam de trabalho extra para imprimir usando printf e use em outro contexto). O uso de qualquer outro tipo de corda não é natural e provavelmente relativamente raro.
Além disso, nessas circunstâncias, seu teste me parece um pouco estranho, pois, para funcionar corretamente (usando strlen), você está assumindo uma string terminada por NUL em primeiro lugar. Você deve testar o caso de cadeias não terminadas por NUL se pretende que sua biblioteca trabalhe com elas.
fonte
Seu argumento de "segurança" realmente não se sustenta. Se você não confia no usuário para entregar a você uma string terminada em nulo quando é isso que você documentou (e qual é "a norma" para C simples), você não pode realmente confiar no tamanho que ele fornece (o que eles provavelmente
strlen
usará exatamente como você está fazendo, se não o tiverem à mão e que falhará se a "string" não fosse uma string em primeiro lugar).Porém, existem razões válidas para exigir um comprimento: se você deseja que suas funções funcionem em substrings, é possivelmente muito mais fácil (e eficiente) passar um comprimento do que fazer com que o usuário faça alguma mágica de cópia para obter o byte nulo no lugar certo (e corra o risco de erros pontuais ao longo do caminho).
Ser capaz de lidar com codificações em que os bytes nulos não são terminações, ou ser capaz de lidar com seqüências que possuem nulos incorporados (de propósito) podem ser úteis em algumas circunstâncias (depende do que exatamente suas funções fazem).
Ser capaz de lidar com dados com terminação não nula (matrizes de comprimento fixo) também é útil.
Em resumo: depende do que você está fazendo na sua biblioteca e de que tipo de dados você espera que seus usuários estejam lidando.
Também há possivelmente um aspecto de desempenho nisso. Se sua função precisar conhecer o comprimento da string com antecedência e você esperar que seus usuários, pelo menos em geral, já conheçam essas informações, fazer com que elas as passem (em vez de calculá-las) pode reduzir alguns ciclos.
Mas se sua biblioteca espera seqüências de texto ASCII comuns comuns e você não tem restrições de desempenho insuportáveis e um entendimento muito bom de como seus usuários irão interagir com sua biblioteca, adicionar um parâmetro de comprimento não parece uma boa idéia. Se a string não for finalizada corretamente, é provável que o parâmetro length seja igualmente falso. Eu não acho que você vai ganhar muito com isso.
fonte
Não. As strings são sempre terminadas em nulo por definição, o comprimento da string é redundante.
Os dados de caracteres com terminação não nula nunca devem ser chamados de "string". Processá-lo (e dar voltas) geralmente deve ser encapsulado em uma biblioteca e não parte da API. É provável que exigir o comprimento como parâmetro apenas para evitar chamadas strlen () únicas. Otimização prematura.
Confiar no chamador de uma função da API não é inseguro ; comportamento indefinido é perfeitamente aceitável se pré-condições documentadas não forem atendidas.
Obviamente, uma API bem projetada não deve conter armadilhas e facilitar o uso correto. E isso significa que deve ser o mais simples e direto possível, evitando redundâncias e seguindo as convenções do idioma.
fonte
Você deve sempre manter seu comprimento por perto. Por um lado, seus usuários podem querer conter NULLs neles. E segundo, não esqueça que
strlen
é O (N) e requer tocar em todo o cache de string bye bye. E, terceiro, facilita a transmissão de subconjuntos - por exemplo, eles podem fornecer menos do que o comprimento real.fonte
strlen
em um teste de loop.)Você deve distinguir entre passar uma string e passar um buffer .
Em C, as strings são tradicionalmente terminadas em NUL. É inteiramente razoável esperar isso. Portanto, geralmente não há necessidade de passar pelo comprimento da corda; pode ser calculado com,
strlen
se necessário.Ao passar em torno de um buffer , especialmente um que é gravado, você deve absolutamente passar o tamanho do buffer. Para um buffer de destino, isso permite que o receptor garanta que ele não exceda o buffer. Para um buffer de entrada, ele permite que o receptor evite ler além do final, especialmente se o buffer de entrada contiver dados arbitrários originários de uma fonte não confiável.
Talvez haja alguma confusão, porque as strings e os buffers poderiam ser
char*
e porque muitas funções de string geram novas strings gravando nos buffers de destino. Algumas pessoas concluem que as funções de string devem ter comprimentos de string. No entanto, esta é uma conclusão imprecisa. A prática de incluir um tamanho em um buffer (seja esse buffer usado para strings, matrizes de números inteiros, estruturas, qualquer que seja) é um mantra mais útil e mais geral.(No caso de ler uma string de uma fonte não confiável (por exemplo, um soquete de rede), é importante fornecer um comprimento, pois a entrada pode não ter a terminação NUL. No entanto , você não deve considerar a entrada como uma string. deve tratá-lo como um buffer de dados arbitrário que pode conter uma string (mas você não sabe até que você realmente a valide), para que isso ainda siga o princípio de que os buffers devem ter tamanhos associados e que as strings não precisam deles.
fonte
Se as funções forem usadas principalmente com literais de seqüência de caracteres, a dificuldade de lidar com comprimentos explícitos pode ser minimizada pela definição de algumas macros. Por exemplo, dada uma função de API:
pode-se definir uma macro:
e invoque-o como mostrado em:
Embora seja possível criar coisas "criativas" para passar a macro que será compilada, mas que na verdade não funcionará, o uso de
""
ambos os lados da string na avaliação de "sizeof" deve capturar tentativas acidentais de usar caracteres. ponteiros que não sejam literais de string decompostos [na ausência deles""
, uma tentativa de passar um ponteiro de caractere daria erroneamente o comprimento do tamanho de um ponteiro, menos um.Uma abordagem alternativa no C99 seria definir um tipo de estrutura "ponteiro e comprimento" e definir uma macro que converta uma literal de cadeia de caracteres em um literal composto desse tipo de estrutura. Por exemplo:
Observe que, se alguém usa essa abordagem, deve passar essas estruturas por valor, em vez de passar seus endereços. Caso contrário, algo como:
pode falhar, pois a vida útil dos literais compostos terminaria no final de suas instruções anexas.
fonte