Determinando o tamanho do buffer do sprintf – qual é o padrão?

Ao converter um int assim:

char a[256]; sprintf(a, "%d", 132); 

Qual é a melhor maneira de determinar quão grande deve ser? Eu suponho que a configuração manual é boa (como eu vi usado em todos os lugares), mas quão grande deve ser? Qual é o maior valor int possível em um sistema de 32 bits, e há alguma maneira complicada de determinar isso em tempo real?

O número máximo possível de bits em um int é CHAR_BIT * sizeof(int) , e um dígito decimal é “worth” pelo menos 3 bits, portanto um limite superior solto no espaço requerido para um int arbitrário é (CHAR_BIT * sizeof(int) / 3) + 3 . Esse +3 é um para o fato de que arredondamos para baixo quando dividimos, um para o sinal, um para o finalizador nulo.

Se por “em um sistema de 32 bits” você quer dizer que você sabe que int é de 32 bits, então você precisa de 12 bytes. 10 para os dígitos, um para o sinal, um para o terminador nul.

No seu caso específico, onde o int a ser convertido é 132 , você precisa de 4 bytes. Badum, tish.

Onde buffers de tamanho fixo podem ser usados ​​com um limite razoável, eles são a opção mais simples. Eu não tão humildemente submeto que o limite acima é razoável (13 bytes em vez de 12 para 32 bits int e 23 bytes em vez de 21 para 64 bits int ). Mas para casos difíceis, no C99 você poderia apenas chamar snprintf para obter o tamanho, então malloc muito. Isso é um exagero para um caso tão simples como este.

Alguns aqui argumentam que essa abordagem é exagerada e, para converter ints para strings, posso estar mais inclinado a concordar. Mas quando não é possível encontrar um limite razoável para o tamanho da string, vi essa abordagem usada e a usei por conta própria.

 int size = snprintf(NULL, 0, "%d", 132); char * a = malloc(size + 1); sprintf(a, "%d", 132); 

Eu vou quebrar o que está acontecendo aqui.

  1. Na primeira linha, queremos determinar quantos caracteres precisamos. Os dois primeiros argumentos para snprintf dizem que eu quero escrever 0 caracteres do resultado para NULL . Quando fizermos isso, o snprintf não escreverá nenhum caracter em lugar algum, simplesmente retornará o número de caracteres que seriam escritos. Isso é o que nós queríamos.
  2. Na segunda linha, estamos alocando dinamicamente memory para um ponteiro de char . Certifique-se de adicionar 1 ao tamanho desejado (para o caractere de término \0 final).
  3. Agora que há memory suficiente alocada para o ponteiro de char , podemos usar com segurança o sprintf para gravar o inteiro no ponteiro de char .

Claro que você pode torná-lo mais conciso, se quiser.

 char * a = malloc(snprintf(NULL, 0, "%d", 132) + 1); sprintf(a, "%d", 132); 

A menos que este seja um programa “rápido e sujo”, você sempre quer certificar-se de liberar a memory que chamou com malloc . Este é o lugar onde a abordagem dinâmica se torna complicada com C. No entanto, IMHO, se você não quer ser alocar grandes pointers char quando na maioria das vezes você só estará usando uma porção muito pequena deles, então eu não acho esta é uma abordagem ruim.

É possível fazer a solução de Daniel Standage funcionar para qualquer número de argumentos usando o vsnprintf que está em C ++ 11 / C99.

 int bufferSize(const char* format, ...) { va_list args; va_start(args, format); int result = vsnprintf(NULL, 0, format, args); va_end(args); return result + 1; // safe byte for \0 } 

Conforme especificado no padrão c99 , seção 7.19.6.12:

A function vsnprintf retorna o número de caracteres que teria sido escrito se tivesse sido suficientemente grande, sem contar o caractere nulo de terminação, ou um valor negativo se um
Ocorreu um erro de codificação.

Eu vejo que esta conversa é um par de anos, mas eu achei ao tentar encontrar uma resposta para o MS VC ++, onde snprintf não pode ser usado para encontrar o tamanho. Vou postar a resposta que finalmente encontrei no caso de ajudar alguém:

O VC ++ tem a function _scprintf especificamente para encontrar o número de caracteres necessários.

Se você estiver imprimindo um inteiro simples e nada mais, existe uma maneira muito mais simples de determinar o tamanho do buffer de saída. Pelo menos computacionalmente mais simples, o código é um pouco obtuso:

 char *text; text = malloc(val ? (int)log10((double)abs(val)) + (val < 0) + 2 : 2); 

log10 (valor) retorna o número de dígitos (menos um) requerido para armazenar um valor positivo diferente de zero na base 10. Ele sai um pouco dos rails para números menores que um, então especificamos abs (), e codificamos a lógica especial para zero (o operador ternário, teste? truecase: falsecase). Adicione um para o espaço para armazenar o sinal de um número negativo (val <0), um para compensar a diferença do log10 e outro para o terminador nulo (para um total de 2), e você acabou de calcular a quantidade exata de espaço de armazenamento necessária para um determinado número, sem chamar snprintf () ou equivalentes duas vezes para realizar o trabalho. Além disso, usa menos memory, geralmente, do que o INT_MAX exigirá.

Se você precisar imprimir muitos números muito rapidamente, não se preocupe em alocar o buffer INT_MAX e depois imprimi-lo repetidamente. Menos memory debulhando é melhor.

Note também que você pode não precisar realmente do (duplo) em oposição a um (float). Eu não me incomodei em verificar. Lançar de um lado para outro assim também pode ser um problema. YMMV em tudo isso. Funciona muito bem para mim, no entanto.

É bom que você esteja preocupado com o tamanho do buffer. Para aplicar esse pensamento no código, eu usaria o snprintf

 snprintf( a, 256, "%d", 132 ); 

ou

 snprintf( a, sizeof( a ), "%d", 132 ); // when a is array, not pointer 

Primeiro, sprintf é o diabo. Se alguma coisa, use snprintf, ou então você corre o risco de destruir a memory e travar seu aplicativo.

Quanto ao tamanho do buffer, é como todos os outros buffers – o menor possível, tão grande quanto necessário. No seu caso, você tem um número inteiro assinado, portanto, obtenha o maior tamanho possível e sinta-se à vontade para adicionar um pouco de preenchimento de segurança. Não há “tamanho padrão”.

Também não depende de qual sistema você está rodando. Se você definir o buffer na pilha (como no seu exemplo), isso depende do tamanho da pilha. Se você mesmo criou o segmento, você mesmo determinou o tamanho da pilha, assim você conhece os limites. Se você espera uma recursion ou um rastreamento de pilha profunda, também é necessário ter cuidado extra.