A quel moment la mémoire est-elle généralement affectée aux variables locales en C++?

je suis en train de déboguer un overflow de pile plutôt étrange prétendument causé par l'allocation de trop grandes variables sur la pile et j'aimerais clarifier ce qui suit.

supposons que j'ai la fonction suivante:

void function()
{
    char buffer[1 * 1024];
    if( condition ) {
       char buffer[1 * 1024];
       doSomething( buffer, sizeof( buffer ) );
    } else {
       char buffer[512 * 1024];
       doSomething( buffer, sizeof( buffer ) );
    }
 }

je comprends, qu'il est dépendant du compilateur et dépend aussi de ce que l'optimiseur décide, mais qu'est-ce que le stratégie typique pour allouer la mémoire pour ces variables locales?

le cas le plus défavorable (1 + 512 kilooctets) sera-t-il attribué immédiatement une fois la fonction entrée ou 1 kilooctet sera-t-il attribué en premier, puis, selon la condition, 1 ou 512 kilooctets seront-ils attribués en plus?

26
demandé sur sharptooth 2011-08-17 11:05:54

5 réponses

vos variables locales (stack) sont attribuées dans le même espace que les cadres stack. Lorsque la fonction est appelée, le pointeur de pile est changé en "make room" pour le cadre de pile. C'est typiquement fait en un seul appel. Si vous consommez de la pile avec des variables locales, vous rencontrerez un débordement de pile.

~512 kbytes est vraiment trop grand pour la pile dans tous les cas; vous devriez allouer cela sur le tas en utilisant std::vector .

4
répondu tenfour 2011-08-17 09:08:17

sur de nombreuses plates-formes/ABIs, la totalité de l'image de série (y compris la mémoire pour chaque variable locale) est attribuée lorsque vous entrez la fonction. Sur d'autres, il est courant de pousser/pop mémoire petit à petit, car il est nécessaire.

bien sûr, dans les cas où la totalité du cadre de Stack est affectée en une seule fois, différents compilateurs peuvent encore décider de différentes tailles de cadre de stack. Dans votre cas, certains compilateurs manqueraient une occasion d'optimisation, et allouer une mémoire unique pour chaque variable locale, même celles qui sont dans différentes branches du code (à la fois le tableau 1 * 1024 et le 512 * 1024 dans votre cas), où un meilleur compilateur d'optimisation ne devrait allouer que la mémoire maximale requise de n'importe quel chemin à travers la fonction (le chemin else dans votre cas, donc allouer un bloc de 512kb devrait être suffisant). Si vous voulez savoir ce que fait votre plateforme, regardez le démontage.

mais il ne voulait pas Surprenez-moi de voir toute la partie de mémoire allouée immédiatement.

15
répondu jalf 2011-08-17 08:49:23

j'ai vérifié sur LLVM :

void doSomething(char*,char*);

void function(bool b)
{
    char b1[1 * 1024];
    if( b ) {
       char b2[1 * 1024];
       doSomething(b1, b2);
    } else {
       char b3[512 * 1024];
       doSomething(b1, b3);
    }
}

Donne:

; ModuleID = '/tmp/webcompile/_28066_0.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-unknown-linux-gnu"

define void @_Z8functionb(i1 zeroext %b) {
entry:
  %b1 = alloca [1024 x i8], align 1               ; <[1024 x i8]*> [#uses=1]
  %b2 = alloca [1024 x i8], align 1               ; <[1024 x i8]*> [#uses=1]
  %b3 = alloca [524288 x i8], align 1            ; <[524288 x i8]*> [#uses=1]
  %arraydecay = getelementptr inbounds [1024 x i8]* %b1, i64 0, i64 0 ; <i8*> [#uses=2]
  br i1 %b, label %if.then, label %if.else

if.then:                                          ; preds = %entry
  %arraydecay2 = getelementptr inbounds [1024 x i8]* %b2, i64 0, i64 0 ; <i8*> [#uses=1]
  call void @_Z11doSomethingPcS_(i8* %arraydecay, i8* %arraydecay2)
  ret void

if.else:                                          ; preds = %entry
  %arraydecay6 = getelementptr inbounds [524288 x i8]* %b3, i64 0, i64 0 ; <i8*> [#uses=1]
  call void @_Z11doSomethingPcS_(i8* %arraydecay, i8* %arraydecay6)
  ret void
}

declare void @_Z11doSomethingPcS_(i8*, i8*)

vous pouvez voir le 3 alloca en haut de la fonction.

je dois admettre que je suis un peu déçu que b2 et b3 ne soient pas pliés ensemble dans L'IR, car un seul d'entre eux ne sera jamais utilisé.

11
répondu Matthieu M. 2011-08-17 11:52:24

cette optimisation est connue sous le nom de" coloration de la pile", parce que vous assignez plusieurs objets de la pile à la même adresse. C'est un domaine que nous savons que la GVL peut améliorer. À l'heure actuelle, LLVM ne le fait que pour les objets empilés créés par l'allocateur register pour les fentes de déversement. Nous aimerions étendre cela pour traiter les variables de pile d'utilisateurs aussi bien, mais nous avons besoin d'un moyen de capturer la durée de vie de la valeur en IR.

il y a une ébauche de comment nous projetons de faire ceci ici: http://nondot.org/sabre/LLVMNotes/MemoryUseMarkers.txt

le travail de mise en œuvre est en cours, plusieurs éléments sont mis en œuvre dans mainline.

- Chris

10
répondu Chris Lattner 2011-08-17 18:20:13

comme vous dites, il est dépendant du compilateur, mais vous pourriez envisager d'utiliser alloca pour surmonter cela. Les variables seront toujours allouées sur la pile, et automatiquement libérées lorsqu'elles sortent de la portée, mais vous prenez le contrôle de quand et si l'espace de la pile est alloué.

alors que l'utilisation d'allaca est typiquement découragée , elle a son utilité dans des situations telles que celles ci-dessus.

0
répondu Shane MacLaughlin 2017-05-23 12:07:36