Estou usando o seguinte código no meu aplicativo e está funcionando bem. Mas estou pensando se é melhor fazê-lo com malloc ou deixá-lo como está?
function (int len)
{
char result [len] = some chars;
send result over network
}
Estou usando o seguinte código no meu aplicativo e está funcionando bem. Mas estou pensando se é melhor fazê-lo com malloc ou deixá-lo como está?
function (int len)
{
char result [len] = some chars;
send result over network
}
Respostas:
A principal diferença é que os VLAs (matrizes de comprimento variável) não fornecem mecanismo para detectar falhas de alocação.
Se você declarar
char result[len];
e len
excede a quantidade de espaço disponível na pilha, o comportamento do seu programa é indefinido. Não existe um mecanismo de linguagem para determinar antecipadamente se a alocação será bem-sucedida ou para determinar após o fato se foi bem-sucedida.
Por outro lado, se você escrever:
char *result = malloc(len);
if (result == NULL) {
/* allocation failed, abort or take corrective action */
}
você pode lidar com falhas normalmente ou, pelo menos, garantir que seu programa não tente continuar executando após uma falha.
(Bem, principalmente. Nos sistemas Linux, é malloc()
possível alocar uma parte do espaço de endereço, mesmo que não haja armazenamento correspondente disponível; tentativas posteriores de usar esse espaço podem invocar o OOM Killer . Mas verificar malloc()
falhas ainda é uma boa prática.)
Outra questão, em muitos sistemas, é que há mais espaço (possivelmente muito mais espaço) disponível do malloc()
que para objetos automáticos como VLAs.
E, como a resposta de Philip já mencionou, os VLAs foram adicionados no C99 (a Microsoft em particular não os suporta).
E os VLAs foram feitos opcionais no C11. Provavelmente a maioria dos compiladores C11 os suportará, mas você não pode contar com isso.
Matrizes automáticas de comprimento variável foram introduzidas em C em C99.
A menos que você tenha preocupações sobre comparabilidade retroativa com padrões mais antigos, tudo bem.
Em geral, se funcionar, não toque. Não otimize com antecedência. Não se preocupe em adicionar recursos especiais ou maneiras inteligentes de fazer as coisas, porque muitas vezes você não vai usá-las. Mantenha simples.
Se o seu compilador suportar matrizes de tamanho variável, o único perigo será transbordar a pilha em alguns sistemas, quando isso len
for ridiculamente grande. Se você tem certeza de que len
não será maior que um determinado número e sabe que sua pilha não excederá o tamanho máximo, deixe o código como está; caso contrário, reescreva-o com malloc
e free
.
char result [sizeof(char)]
é uma matriz de tamanho 1
(porque sizeof(char)
é igual a um), portanto, a atribuição será truncada some chars
.
str
decai para um ponteiro , então sizeof
será quatro ou oito, dependendo do tamanho do ponteiro no seu sistema.
char* result = alloca(len);
, que é alocado na pilha. Tem o mesmo efeito básico (e mesmos problemas básicos)
Gosto da ideia de que você pode ter uma matriz alocada em tempo de execução sem fragmentação da memória, ponteiros oscilantes etc. No entanto, outros apontaram que essa alocação em tempo de execução pode falhar silenciosamente. Então, eu tentei isso usando o gcc 4.5.3 em um ambiente de Cygwin bash:
#include <stdio.h>
#include <string.h>
void testit (unsigned long len)
{
char result [len*2];
char marker[100];
memset(marker, 0, sizeof(marker));
printf("result's size: %lu\n", sizeof(result));
strcpy(result, "this is a test that should overflow if no allocation");
printf("marker's contents: '%s'\n", marker);
}
int main(int argc, char *argv[])
{
testit(100);
testit((unsigned long)-1); // probably too big
}
A saída foi:
$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'
O tamanho excessivamente grande passado na segunda chamada causou claramente a falha (transbordando para o marcador []). Isso não significa que esse tipo de verificação seja à prova de idiotas (os tolos podem ser inteligentes!) Ou que atenda aos padrões da C99, mas pode ajudar se você tiver essa preocupação.
Como sempre, YMMV.
De um modo geral, a pilha é o melhor e mais fácil local para colocar seus dados.
Eu evitaria os problemas dos VLAs simplesmente alocando a maior matriz que você espera.
No entanto, existem casos em que a pilha é melhor e mexer com o malloc vale o esforço.
Na programação incorporada, sempre usamos matriz estática em vez de malloc quando as operações malloc e free são frequentes. Por causa da falta de gerenciamento de memória no sistema incorporado, a alocação frequente e as operações livres causarão fragmento de memória. Mas devemos utilizar alguns métodos complicados, como definir o tamanho máximo da matriz e usar a matriz local estática.
Se seu aplicativo estiver em execução no Linux ou Windows, não importa o uso de matriz ou malloc. O ponto principal é onde você usa sua estrutura de datas e sua lógica de código.
Algo que ninguém mencionou ainda é que a opção de matriz de comprimento variável provavelmente será muito mais rápida que malloc / free, pois alocar um VLA é apenas um caso de ajustar o ponteiro da pilha (pelo menos no GCC).
Portanto, se essa função for chamada com frequência (que você naturalmente determinará por criação de perfil), o VLA é uma boa opção de otimização.
Esta é uma solução C muito comum que uso para o problema que pode ser útil. Ao contrário dos VLAs, ele não enfrenta nenhum risco prático de estouro de pilha em casos patológicos.
/// Used for frequent allocations where the common case generally allocates
/// a small amount of memory, at which point a heap allocation can be
/// avoided, but rare cases also need to be handled which may allocate a
/// substantial amount. Note that this structure is not safe to copy as
/// it could potentially invalidate the 'data' pointer. Its primary use
/// is just to allow the stack to be used in common cases.
struct FastMem
{
/// Stores raw bytes for fast access.
char fast_mem[512];
/// Points to 'fast_mem' if the data fits. Otherwise, it will point to a
/// dynamically allocated memory address.
void* data;
};
/// @return A pointer to a newly allocated memory block of the specified size.
/// If the memory fits in the specified fast memory structure, it will use that
/// instead of the heap.
void* fm_malloc(struct FastMem* mem, int size)
{
// Utilize the stack if the memory fits, otherwise malloc.
mem->data = (size < sizeof mem->fast_mem) ? mem->fast_mem: malloc(size);
return mem->data;
}
/// Frees the specified memory block if it has been allocated on the heap.
void fm_free(struct FastMem* mem)
{
// Free the memory if it was allocated dynamically with 'malloc'.
if (mem->data != mem->fast_mem)
free(mem->data);
mem->data = 0;
}
Para usá-lo no seu caso:
struct FastMem fm;
// `result` will be allocated on the stack if 'len <= 512'.
char* result = fm_malloc(&fm, len);
// send result over network.
...
// this function will only do a heap deallocation if 'len > 512'.
fm_free(&fm, result);
O que isso faz no caso acima é usar a pilha se a string se encaixar em 512 bytes ou menos. Caso contrário, ele usa uma alocação de heap. Isso pode ser útil se, digamos, 99% do tempo, a string se encaixar em 512 bytes ou menos. No entanto, digamos que exista algum caso exótico maluco que você possa ocasionalmente precisar controlar onde a string tem 32 kilobytes em que o usuário adormeceu no teclado ou algo assim. Isso permite que ambas as situações sejam tratadas sem problemas.
A versão real eu uso na produção também tem a sua própria versão do realloc
e calloc
e assim por diante, bem como estruturas de dados padrão conformes C ++ construídos no mesmo conceito, mas eu extraído o mínimo necessário para ilustrar o conceito.
Ele tem a ressalva de que é perigoso copiar e você não deve retornar ponteiros alocados por ele (eles podem acabar sendo invalidados à medida que a FastMem
instância é destruída). Ele deve ser usado para casos simples no escopo de uma função local, onde você seria tentado a sempre usar a pilha / VLAs, caso contrário, em casos raros, isso poderá causar estouros de buffer / pilha. Não é um alocador de uso geral e não deve ser usado como tal.
Na verdade, eu o criei há tempos em resposta a uma situação em uma base de código herdada usando o C89 que uma equipe anterior pensava que nunca aconteceria onde um usuário conseguisse nomear um item com um nome com mais de 2047 caracteres (talvez ele tenha adormecido no teclado ) Meus colegas realmente tentaram aumentar o tamanho das matrizes alocadas em vários locais para 16.384 em resposta, quando pensei que estava ficando ridículo e apenas trocando um risco maior de estouro de pilha em troca de menor risco de estouro de buffer. Isso forneceu uma solução muito fácil de conectar para corrigir esses casos, apenas adicionando algumas linhas de código. Isso permitiu que o caso comum fosse tratado com muita eficiência e ainda utilizasse a pilha, sem os casos malucos e raros que exigiam que a pilha travasse o software. No entanto, eu achei útil desde então, mesmo depois do C99, já que os VLAs ainda não podem nos proteger contra estouros de pilha. Este pode, mas ainda assim, agrupar da pilha para pequenas solicitações de alocação.
A pilha de chamadas é sempre limitada. Nos sistemas operacionais convencionais, como Linux ou Windows, o limite é de um ou alguns megabytes (e você pode encontrar maneiras de alterá-lo). Com alguns aplicativos multithread, ele pode ser menor (porque os threads podem ser criados com uma pilha menor). Em sistemas embarcados, pode ser tão pequeno quanto alguns kilobytes. Uma boa regra é evitar quadros de chamada maiores que alguns kilobytes.
Portanto, usar um VLA faz sentido apenas se você tiver certeza de que len
é pequeno o suficiente (no máximo algumas dezenas de milhares). Caso contrário, você terá um estouro de pilha e esse é um caso de comportamento indefinido , uma situação muito assustadora .
No entanto, o uso manual da alocação de memória dinâmica C (por exemplo, calloc
ou malloc
&free
) também tem suas desvantagens:
pode falhar e você deve sempre testar a falha (por exemplo, calloc
ou malloc
retornar NULL
).
é mais lento: uma alocação bem-sucedida de VLA leva alguns nanossegundos, uma bem-sucedida malloc
pode precisar de vários microssegundos (nos bons casos, apenas uma fração de microssegundo) ou até mais (em casos patológicos que envolvem trocas , muito mais).
é muito mais difícil codificar: você pode free
apenas quando tiver certeza de que a zona apontada não será mais usada. No seu caso, você pode ligar para os dois calloc
e free
na mesma rotina.
Se você sabe que na maioria das vezes o seu result
(um nome muito ruim, você nunca deve retornar o endereço de uma variável automática VLA; portanto, eu estou usando em buf
vez do result
abaixo) é pequeno, você pode especial, por exemplo:
char tinybuf[256];
char *buf = (len<sizeof(tinybuf))?tinybuf:malloc(len);
if (!buf) { perror("malloc"); exit(EXIT_FAILURE); };
fill_buffer(buf, len);
send_buffer_on_network(buf, len);
if (buf != tinybuf)
free(buf);
No entanto, o código acima é menos legível e provavelmente é uma otimização prematura. No entanto, é mais robusto que uma solução VLA pura.
PS. Alguns sistemas (por exemplo, algumas distribuições Linux estão ativando por padrão) têm um comprometimento excessivo da memória (o que torna o malloc
fornecimento de algum ponteiro, mesmo que não haja memória suficiente). Esse é um recurso que eu não gosto e geralmente desabilito em minhas máquinas Linux.