J'utilise le code suivant dans mon application, et cela fonctionne bien. Mais je me demande s'il vaut mieux le faire avec du malloc ou le laisser tel quel?
function (int len)
{
char result [len] = some chars;
send result over network
}
J'utilise le code suivant dans mon application, et cela fonctionne bien. Mais je me demande s'il vaut mieux le faire avec du malloc ou le laisser tel quel?
function (int len)
{
char result [len] = some chars;
send result over network
}
Réponses:
La principale différence est que les VLA (tableaux de longueur variable) ne fournissent aucun mécanisme pour détecter les échecs d'allocation.
Si vous déclarez
char result[len];
et len
dépasse la quantité d'espace disponible sur la pile, le comportement de votre programme n'est pas défini. Il n'y a pas de mécanisme linguistique pour déterminer à l'avance si l'allocation réussira ou pour déterminer après coup si elle a réussi.
En revanche, si vous écrivez:
char *result = malloc(len);
if (result == NULL) {
/* allocation failed, abort or take corrective action */
}
alors vous pouvez gérer les échecs avec élégance, ou au moins garantir que votre programme ne tentera pas de continuer à s'exécuter après un échec.
(Eh bien, la plupart du temps. Sur les systèmes Linux, malloc()
peut allouer un morceau d'espace d'adressage même s'il n'y a pas de stockage correspondant disponible; les tentatives ultérieures d'utiliser cet espace peuvent invoquer OOM Killer . Mais la recherche d' malloc()
échec est toujours une bonne pratique.)
Un autre problème, sur de nombreux systèmes, est qu'il y a plus d'espace (peut-être beaucoup plus d'espace) disponible malloc()
que pour les objets automatiques comme les VLA.
Et comme la réponse de Philip l'a déjà mentionné, des VLA ont été ajoutés dans C99 (Microsoft en particulier ne les prend pas en charge).
Et les VLA ont été rendus facultatifs dans C11. La plupart des compilateurs C11 les prendront probablement en charge, mais vous ne pouvez pas compter dessus.
Les tableaux automatiques de longueur variable ont été introduits dans C en C99.
À moins que vous n'ayez des inquiétudes quant à la comparabilité en amont avec les anciennes normes, tout va bien.
En général, si cela fonctionne, ne le touchez pas. N'optimisez pas à l'avance. Ne vous inquiétez pas de l'ajout de fonctionnalités spéciales ou de façons intelligentes de faire les choses, car vous n'allez souvent pas l'utiliser. Rester simple.
Si votre compilateur prend en charge les tableaux de longueur variable, le seul danger est de faire déborder la pile sur certains systèmes, lorsque la len
taille est ridiculement grande. Si vous savez avec certitude que cela len
ne sera pas supérieur à un certain nombre et que vous savez que votre pile ne va pas déborder même à la longueur maximale, laissez le code tel quel; sinon, réécrivez-le avec malloc
et free
.
char result [sizeof(char)]
est un tableau de taille 1
(car sizeof(char)
égal à un), donc l'affectation va être tronquée some chars
.
str
se désintègre en un pointeur , donc ça sizeof
va être quatre ou huit, selon la taille du pointeur sur votre système.
char* result = alloca(len);
, qui alloue sur la pile. Il a le même effet de base (et les mêmes problèmes de base)
J'aime l'idée que vous pouvez avoir un tableau alloué au moment de l'exécution sans fragmentation de la mémoire, pointeurs pendants, etc. Cependant, d'autres ont souligné que cette allocation au moment de l'exécution peut échouer en silence. J'ai donc essayé ceci en utilisant gcc 4.5.3 dans un environnement bash Cygwin:
#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
}
Le résultat était:
$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'
La longueur trop grande passée dans le deuxième appel a clairement causé l'échec (débordement dans le marqueur []). Cela ne signifie pas que ce type de vérification est infaillible (les imbéciles peuvent être intelligents!) Ou qu'il répond aux normes de C99, mais cela pourrait aider si vous avez cette préoccupation.
Comme d'habitude, YMMV.
De manière générale, la pile est l'endroit le plus simple et le meilleur pour mettre vos données.
J'éviterais les problèmes des VLA en allouant simplement la plus grande baie que vous attendiez.
Il y a cependant des cas où le tas est meilleur et jouer avec malloc en vaut la chandelle.
Dans la programmation intégrée, nous utilisons toujours un tableau statique au lieu de malloc lorsque les opérations malloc et free sont fréquentes. En raison du manque de gestion de la mémoire dans le système embarqué, les opérations d'allocation et de libération fréquentes entraîneront un fragment de mémoire. Mais nous devons utiliser certaines méthodes délicates telles que la définition de la taille maximale du tableau et l'utilisation d'un tableau local statique.
Si votre application s'exécute sous Linux ou Windows, peu importe l'utilisation de array ou malloc. Le point clé réside dans l'endroit où vous utilisez votre structure de date et votre logique de code.
Quelque chose que personne n'a encore mentionné est que l'option de tableau de longueur variable sera probablement beaucoup plus rapide que malloc / free car l'allocation d'un VLA est juste un cas d'ajustement du pointeur de pile (dans GCC au moins).
Donc, si cette fonction est appelée fréquemment (que vous déterminerez bien sûr par profilage), le VLA est une bonne option d'optimisation.
Il s'agit d'une solution C très courante que j'utilise pour le problème qui pourrait être utile. Contrairement aux VLA, il ne fait face à aucun risque pratique de débordement de pile dans les cas pathologiques.
/// 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;
}
Pour l'utiliser dans votre cas:
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);
Dans le cas ci-dessus, cela utilise la pile si la chaîne tient en 512 octets ou moins. Sinon, il utilise une allocation de tas. Cela peut être utile si, disons, 99% du temps, la chaîne tient en 512 octets ou moins. Cependant, disons qu'il y a un cas exotique fou que vous pourriez parfois avoir besoin de gérer lorsque la chaîne fait 32 kilo-octets où l'utilisateur s'est endormi sur son clavier ou quelque chose comme ça. Cela permet de gérer les deux situations sans problème.
La version actuelle que j'utilise dans la production a également sa propre version de realloc
et calloc
et ainsi de suite, ainsi que des structures de données conformes au standard C ++ construit sur le même concept, mais j'extrait le minimum nécessaire pour illustrer le concept.
Il a la mise en garde qu'il est dangereux de copier et vous ne devez pas renvoyer les pointeurs alloués à travers lui (ils pourraient finir par être invalidés lorsque l' FastMem
instance est détruite). Il est destiné à être utilisé pour des cas simples dans la portée d'une fonction locale où vous seriez tenté de toujours utiliser la pile / VLA, sinon où certains cas rares pourraient provoquer des débordements de tampon / pile. Ce n'est pas un allocateur à usage général et ne doit pas être utilisé comme tel.
Je l'ai en fait créé il y a longtemps en réponse à une situation dans une base de code héritée utilisant C89 qu'une ancienne équipe pensait ne jamais arriver lorsqu'un utilisateur parviendrait à nommer un élément avec un nom de plus de 2047 caractères (peut-être s'est-il endormi sur son clavier ). Mes collègues ont en fait essayé d'augmenter la taille des tableaux alloués à divers endroits à 16384 en réponse, à quel point je pensais que cela devenait ridicule et échangeait simplement un plus grand risque de débordement de pile en échange d'un moindre risque de débordement de tampon. Cela a fourni une solution très facile à brancher pour corriger ces cas en ajoutant simplement quelques lignes de code. Cela a permis de gérer le cas commun très efficacement et d'utiliser toujours la pile sans ces rares cas fous qui exigeaient que le tas écrase le logiciel. Cependant, je' Je l'ai trouvé utile depuis lors, même après C99, car les VLA ne peuvent toujours pas nous protéger contre les débordements de pile. Celui-ci peut mais toujours des pools de la pile pour les petites demandes d'allocation.
La pile d'appels est toujours limitée. Sur les systèmes d'exploitation courants comme Linux ou Windows, la limite est de un ou de quelques mégaoctets (et vous pouvez trouver des moyens de la changer). Avec certaines applications multithreads, il peut être inférieur (car les threads peuvent être créés avec une pile plus petite). Sur les systèmes embarqués, elle peut atteindre quelques kilo-octets. Une bonne règle d'or consiste à éviter les trames d'appel supérieures à quelques kilo-octets.
Donc, utiliser un VLA n'a de sens que si vous êtes sûr qu'il len
est assez petit (au plus quelques dizaines de milliers). Sinon, vous avez un débordement de pile et c'est un cas de comportement indéfini , une situation très effrayante .
Cependant, l'utilisation de l' allocation de mémoire dynamique C manuelle (par exemple calloc
ou malloc
&free
) a aussi ses inconvénients:
il peut échouer et vous devez toujours tester l'échec (par exemple calloc
ou malloc
revenir NULL
).
elle est plus lente: une allocation VLA réussie prend quelques nanosecondes, une réussite malloc
peut nécessiter plusieurs microsecondes (dans les bons cas, seulement une fraction de microseconde) ou même plus (dans les cas pathologiques impliquant une raclée , bien plus).
c'est beaucoup plus difficile à coder: vous ne pouvez le faire free
que lorsque vous êtes sûr que la zone pointée n'est plus utilisée. Dans votre cas, vous pourriez appeler les deux calloc
et free
dans la même routine.
Si vous savez que la plupart du temps votre result
(un nom très médiocre, vous ne devriez jamais retourner l'adresse d'une variable automatique VLA; donc j'utilise buf
au lieu de result
ci - dessous) est petite, vous pouvez le cas particulier, par exemple
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);
Cependant, le code ci-dessus est moins lisible et est probablement une optimisation prématurée. Elle est cependant plus robuste qu'une solution VLA pure.
PS. Certains systèmes (par exemple, certaines distributions Linux activent par défaut) ont un sur-engagement de mémoire (ce qui permet de malloc
donner un pointeur même s'il n'y a pas assez de mémoire). C'est une fonctionnalité que je n'aime pas et que je désactive généralement sur mes machines Linux.