Printf () alloue-t-il de la mémoire en C?

Cette méthode simple crée simplement un tableau de taille dynamic n et l’initialise avec les valeurs 0 … n-1. Il contient une erreur, malloc () alloue juste n au lieu de sizeof (int) * n octets:

int *make_array(size_t n) { int *result = malloc(n); for (int i = 0; i < n; ++i) { //printf("%d", i); result[i] = i; } return result; } int main() { int *result = make_array(8); for (int i = 0; i < 8; ++i) { printf("%d ", result[i]); } free(result); } 

Lorsque vous vérifiez le résultat, vous verrez qu’il imprimera certains chiffres comme prévu, mais les derniers sont du charabia. Cependant, une fois que j’ai inséré printf () dans la boucle, la sortie était étrangement correcte, même si l’allocation était toujours fausse! Existe-t-il une sorte d’allocation de mémoire associée à printf ()?

Ssortingctement, pour répondre à la question dans le titre, la réponse serait que cela dépend de la mise en œuvre. Certaines implémentations peuvent allouer de la mémoire, d’autres non.

Bien qu’il existe d’autres problèmes inhérents à votre code, sur lesquels je vais développer ci-dessous.


Note: c’était à l’origine une série de commentaires que j’ai faits sur la question. J’ai décidé que c’était trop pour un commentaire et les ai déplacés à cette réponse.


Lorsque vous vérifiez le résultat, vous verrez qu’il imprimera certains chiffres comme prévu, mais les derniers sont du charabia.

Je pense que sur les systèmes utilisant un modèle de mémoire segmentée, les allocations sont “arrondies” à une certaine taille. Par exemple, si vous allouez X octets, votre programme sera effectivement propriétaire de ces octets X. Cependant, vous pourrez également (à tort) utiliser ces octets X pendant un certain temps avant que le processeur ne remarque que vous violez des limites et envoie un SIGSEGV. .

C’est probablement pourquoi votre programme ne plante pas dans votre configuration particulière. Notez que les 8 octets que vous avez alloués ne couvrent que deux ints sur les systèmes où sizeof (int) est 4. Les 24 autres octets nécessaires pour les 6 autres ints n’appartiennent pas à votre tableau. Tout élément peut donc écrire dans cet espace. lire à partir de cet espace, vous allez obtenir des ordures, si votre programme ne se bloque pas en premier, c’est.

Le nombre 6 est important. Rappelez-le pour plus tard!

La partie magique est que le tableau résultant aura alors les bons numéros à l’intérieur, le printf imprimera simplement chaque nombre une autre fois. Mais cela change le tableau.

Remarque: Ce qui suit est une spéculation, et je suppose également que vous utilisez la glibc sur un système 64 bits. Je vais append ceci parce que je pense que cela pourrait vous aider à comprendre les raisons possibles pour lesquelles quelque chose peut sembler fonctionner correctement, tout en étant incorrect.

La raison pour laquelle il est “magiquement correct” est probablement due au fait que printf reçoive ces numéros via va_args. printf est probablement en train de remplir la zone de mémoire juste au-delà de la limite physique du tableau (car vprintf alloue de la mémoire pour effectuer l’opération “itoa” nécessaire à l’impression i ). En d’autres termes, ces résultats “corrects” ne sont en réalité que des ordures qui “semblent correctes”, mais en réalité, c’est ce qui se passe dans la RAM. Si vous essayez de changer int en long tout en conservant l’allocation de 8 octets, votre programme sera plus susceptible de planter, car long est plus long que int .

L’implémentation glibc de malloc a une optimisation dans laquelle il alloue une page entière du kernel chaque fois qu’il manque de tas. Cela le rend plus rapide car au lieu de demander plus de mémoire au kernel pour chaque allocation, il peut simplement récupérer la mémoire disponible dans le “pool” et créer un autre “pool” lorsque le premier est plein.

Cela dit, comme pour la stack, les pointeurs de tas de malloc, provenant d’un pool de mémoire, ont tendance à être contigus (ou du moins très proches les uns des autres). Cela signifie que les appels de printf à malloc apparaîtront probablement juste après les 8 octets que vous avez alloués pour votre tableau int. Peu importe comment cela fonctionne, cependant, le fait est que peu importe la pertinence des résultats, ils ne sont en fait que des ordures et que vous invoquez un comportement indéfini, il est donc impossible de savoir ce qui va se passer, ou si le programme fera autre chose dans différentes circonstances, comme un crash ou un comportement inattendu.


J’ai donc essayé d’exécuter votre programme avec et sans printf, et les résultats étaient faux à chaque fois.

 # without printf $ ./a.out 0 1 2 3 4 5 1041 0 

Pour quelque raison que ce soit, rien n’interférait avec le maintien de la mémoire 2..5 . Cependant, quelque chose a interféré avec la mémoire contenant 6 et 7 . Je suppose que c’est le tampon de vprintf utilisé pour créer une représentation sous forme de chaîne des nombres. 1041 serait le texte, et 0 serait le terminateur nul, '\0' . Même si ce n’est pas le résultat de vprintf, quelque chose s’écrit à cette adresse entre la population et l’impression du tableau.

 # with printf $ ./a.out *** Error in `./a.out': free(): invalid next size (fast): 0x0000000000be4010 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x77725)[0x7f9e5a720725] /lib/x86_64-linux-gnu/libc.so.6(+0x7ff4a)[0x7f9e5a728f4a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f9e5a72cabc] ./a.out[0x400679] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f9e5a6c9830] ./a.out[0x4004e9] ======= Memory map: ======== 00400000-00401000 r-xp 00000000 08:02 1573060 /tmp/a.out 00600000-00601000 r--p 00000000 08:02 1573060 /tmp/a.out 00601000-00602000 rw-p 00001000 08:02 1573060 /tmp/a.out 00be4000-00c05000 rw-p 00000000 00:00 0 [heap] 7f9e54000000-7f9e54021000 rw-p 00000000 00:00 0 7f9e54021000-7f9e58000000 ---p 00000000 00:00 0 7f9e5a493000-7f9e5a4a9000 r-xp 00000000 08:02 7995396 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f9e5a4a9000-7f9e5a6a8000 ---p 00016000 08:02 7995396 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f9e5a6a8000-7f9e5a6a9000 rw-p 00015000 08:02 7995396 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f9e5a6a9000-7f9e5a869000 r-xp 00000000 08:02 7999934 /lib/x86_64-linux-gnu/libc-2.23.so 7f9e5a869000-7f9e5aa68000 ---p 001c0000 08:02 7999934 /lib/x86_64-linux-gnu/libc-2.23.so 7f9e5aa68000-7f9e5aa6c000 r--p 001bf000 08:02 7999934 /lib/x86_64-linux-gnu/libc-2.23.so 7f9e5aa6c000-7f9e5aa6e000 rw-p 001c3000 08:02 7999934 /lib/x86_64-linux-gnu/libc-2.23.so 7f9e5aa6e000-7f9e5aa72000 rw-p 00000000 00:00 0 7f9e5aa72000-7f9e5aa98000 r-xp 00000000 08:02 7999123 /lib/x86_64-linux-gnu/ld-2.23.so 7f9e5ac5e000-7f9e5ac61000 rw-p 00000000 00:00 0 7f9e5ac94000-7f9e5ac97000 rw-p 00000000 00:00 0 7f9e5ac97000-7f9e5ac98000 r--p 00025000 08:02 7999123 /lib/x86_64-linux-gnu/ld-2.23.so 7f9e5ac98000-7f9e5ac99000 rw-p 00026000 08:02 7999123 /lib/x86_64-linux-gnu/ld-2.23.so 7f9e5ac99000-7f9e5ac9a000 rw-p 00000000 00:00 0 7ffc30384000-7ffc303a5000 rw-p 00000000 00:00 0 [stack] 7ffc303c9000-7ffc303cb000 r--p 00000000 00:00 0 [vvar] 7ffc303cb000-7ffc303cd000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 012345670 1 2 3 4 5 6 7 Aborted 

C’est la partie intéressante. Vous n’avez pas mentionné dans votre question si votre programme s’est écrasé. Mais quand je l’ai couru, il s’est écrasé. Dur

C’est aussi une bonne idée de vérifier avec valgrind, si vous en avez. Valgrind est un programme utile qui indique comment vous utilisez votre mémoire. Voici la sortie de valgrind:

 $ valgrind ./a.out ==5991== Memcheck, a memory error detector ==5991== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==5991== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==5991== Command: ./a.out ==5991== ==5991== Invalid write of size 4 ==5991== at 0x4005F2: make_array (in /tmp/a.out) ==5991== by 0x40061A: main (in /tmp/a.out) ==5991== Address 0x5203048 is 0 bytes after a block of size 8 alloc'd ==5991== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==5991== by 0x4005CD: make_array (in /tmp/a.out) ==5991== by 0x40061A: main (in /tmp/a.out) ==5991== ==5991== Invalid read of size 4 ==5991== at 0x40063C: main (in /tmp/a.out) ==5991== Address 0x5203048 is 0 bytes after a block of size 8 alloc'd ==5991== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==5991== by 0x4005CD: make_array (in /tmp/a.out) ==5991== by 0x40061A: main (in /tmp/a.out) ==5991== 0 1 2 3 4 5 6 7 ==5991== ==5991== HEAP SUMMARY: ==5991== in use at exit: 0 bytes in 0 blocks ==5991== total heap usage: 2 allocs, 2 frees, 1,032 bytes allocated ==5991== ==5991== All heap blocks were freed -- no leaks are possible ==5991== ==5991== For counts of detected and suppressed errors, rerun with: -v ==5991== ERROR SUMMARY: 12 errors from 2 contexts (suppressed: 0 from 0) 

Comme vous pouvez le constater, valgrind signale que vous avez une invalid write of size 4 et une invalid read of size 4 (4 octets est la taille d’un int sur mon système). Il est également mentionné que vous lisez un bloc de taille 0 qui vient après un bloc de taille 8 (le bloc que vous avez malloc’d). Cela vous indique que vous dépassez le tableau et que vous vous retrouvez dans des déchets. Vous remarquerez peut-être aussi qu’il a généré 12 erreurs dans 2 contextes. Plus précisément, il s’agit de 6 erreurs dans un contexte d’écriture et de 6 erreurs dans un contexte de lecture. Exactement la quantité d’espace non alloué que j’ai mentionné plus tôt.

Voici le code corrigé:

 #include  #include  int *make_array(size_t n) { int *result = malloc(n * sizeof (int)); // Notice the sizeof (int) for (int i = 0; i < n; ++i) result[i] = i; return result; } int main() { int *result = make_array(8); for (int i = 0; i < 8; ++i) printf("%d ", result[i]); free(result); return 0; } 

Et voici la sortie de valgrind:

 $ valgrind ./a.out ==9931== Memcheck, a memory error detector ==9931== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==9931== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==9931== Command: ./a.out ==9931== 0 1 2 3 4 5 6 7 ==9931== ==9931== HEAP SUMMARY: ==9931== in use at exit: 0 bytes in 0 blocks ==9931== total heap usage: 2 allocs, 2 frees, 1,056 bytes allocated ==9931== ==9931== All heap blocks were freed -- no leaks are possible ==9931== ==9931== For counts of detected and suppressed errors, rerun with: -v ==9931== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 

Notez qu'il ne rapporte aucune erreur et que les résultats sont corrects.

Si printf() alloue de la mémoire au cours de l’exécution de son travail, cela n’est pas spécifié. Il ne serait pas étonnant qu’une implémentation donnée l’ait fait, mais il n’y a aucune raison de supposer que ce soit le cas. De plus, si une implémentation le fait, cela ne dit rien sur le fait de savoir si une implémentation différente le fait

Le fait que vous voyiez un comportement différent lorsque printf() est à l’intérieur de la boucle ne vous dit rien. Le programme présente un comportement indéfini en dépassant les limites d’un object alloué. Une fois que cela est fait, tout comportement ultérieur est indéfini. Vous ne pouvez pas raisonner à propos d’un comportement indéfini, du moins pas en termes de sémantique C. Le programme n’a pas de sémantique C une fois qu’un comportement indéfini a commencé. C’est ce que “indéfini” signifie.

Vous allouez 8 octets pour le tableau, mais vous en stockez 8, dont chacun fait au moins 2 octets (probablement 4). Vous écrivez donc au-delà de la fin de la mémoire allouée. Cela invoque un comportement indéfini.

Lorsque vous invoquez un comportement indéfini, tout peut arriver. Votre programme peut se bloquer, il peut afficher des résultats inattendus ou sembler fonctionner correctement. Un changement apparemment sans rapport peut changer laquelle des actions ci-dessus se produit.

Corrigez l’allocation de mémoire et votre code fonctionnera comme prévu.

 int *result = malloc(sizeof(int) * n);