93 votes

Pourquoi un lambda ont une taille de 1 octet ?

Je suis en train de travailler avec la mémoire de certains lambdas en C++, mais je suis un peu perplexe par leur taille.

Voici mon code de test:

#include <iostream>
#include <string>

int main()
{
  auto f = [](){ return 17; };
  std::cout << f() << std::endl;
  std::cout << &f << std::endl;
  std::cout << sizeof(f) << std::endl;
}

Vous pouvez l'exécuter ici: http://fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598

La sortie est:

17
0x7d90ba8f626f
1

Ceci suggère que la taille de mon lambda est de 1.

  • Comment est-ce possible?

  • Ne pas le lambda être, au minimum, un pointeur vers sa mise en œuvre?

115voto

Yakk Points 31636

Le lambda en question a en fait pas état.

Examiner:

struct lambda {
  auto operator()() const { return 17; }
};

Et si l'on avait lambda f;, c'est une classe vide. Non seulement le ci-dessus lambda fonctionnellement similaire à votre lambda, c'est (en gros) comment votre lambda est mis en œuvre! (Elle a aussi besoin d'un cast implicite de pointeur de fonction de l'opérateur, et le nom de l' lambda va être remplacé par certains généré par le compilateur pseudo-guid)

En C++, les objets ne sont pas des pointeurs. Ils sont de véritables choses. Ils utilisent uniquement de l'espace nécessaire pour stocker les données. Un pointeur vers un objet peut être plus grand qu'un objet.

Alors que vous pourriez penser que la lambda comme un pointeur à une fonction, il n'est pas. Vous ne pouvez pas réaffecter l' auto f = [](){ return 17; }; d'une autre fonction ou lambda!

 auto f = [](){ return 17; };
 f = [](){ return -42; };

le ci-dessus est illégal. Il n'y a pas de place dans f de store dont la fonction va être appelé, cette information est stocké dans le type d' f, pas de la valeur de f!

Si vous n'avez ceci:

int(*f)() = [](){ return 17; };

ou ceci:

std::function<int()> f = [](){ return 17; };

vous n'êtes plus le stockage de l'lambda directement. Dans ces deux cas, f = [](){ return -42; } est légal -- donc, dans ces cas, nous sommes de stockage dont la fonction nous sommes à l'invocation de la valeur de f. Et sizeof(f) n'est plus 1, mais plutôt sizeof(int(*)()) ou plus (en gros, être un pointeur de taille moyenne ou plus grand, comme vous l'attendez. std::function a une taille minimale implicite par la norme (ils doivent être en mesure de stocker "à l'intérieur d'eux-mêmes" callables jusqu'à une certaine taille) qui est au moins aussi grand qu'un pointeur de fonction dans la pratique).

Dans l' int(*f)() des cas, il s'agit d'un pointeur de fonction vers une fonction qui se comporte comme-si-vous appelé lambda. Cela ne fonctionne que pour les apatrides lambdas (avec un vide [] liste de capture).

Dans l' std::function<int()> f des cas, vous créez un type effacement de la classe std::function<int()> instance (dans ce cas) utilise le placement de nouvelles pour stocker une copie de la taille-1 lambda dans une mémoire tampon interne (et, si un plus grand lambda a été adoptée (avec plus d'état), d'allocation de tas).

Comme une supposition, quelque chose comme celles-ci est probablement ce que vous pensez qui se passe. Qu'un lambda est un objet dont le type est décrit par sa signature. En C++, il a été décidé de faire des lambdas zéro coût des abstractions sur le manuel de la fonction de l'objet de la mise en œuvre. Cela vous permet de passer un lambda en std algorithme (ou similaires) et son contenu sera entièrement visible par le compilateur lorsqu'il instancie l'algorithme de modèle. Si un lambda avait un type comme std::function<void(int)>, son contenu ne serait pas entièrement visible, et fabriqués à la main la fonction de l'objet pourrait être plus rapide.

L'objectif de normalisation de C++ est de haut niveau de la programmation avec zéro frais généraux plus fabriqués à la main C code.

Maintenant que vous comprenez que votre f est en fait apatrides, il devrait y avoir une autre question dans votre tête: le lambda n'a pas d'état. Pourquoi n'est-il pas la taille ont 0?


Il est la réponse la plus courte.

Tous les objets en C++ doit avoir une taille minimum de 1 en vertu de la norme, et deux objets de même type ne peuvent pas avoir la même adresse. Ceux-ci sont connectés, car un tableau de type T aura les éléments placés sizeof(T) d'intervalle.

Maintenant, comme il n'a pas d'état, il peut parfois prendre jusqu'à pas d'espace. Cela ne se fait pas quand il est "seul", mais dans certains contextes, il peut arriver. std::tuple et similaires de la bibliothèque de code exploits de ce fait. Voici comment il fonctionne:

En tant que lambda est équivalent à une classe avec un operator() surchargé, apatrides lambdas (avec un [] liste de capture) sont toutes vides classes. Ils ont sizeof de 1. En fait, si vous héritez d'eux (ce qui est permis!), ils prennent pas de place tant qu'il ne cause pas le même type de adresse de collision. (Ceci est connu comme le vide optimisation de la base).

template<class T>
struct toy:T {
  toy(toy const&)=default;
  toy(toy &&)=default;
  toy(T const&t):T(t) {}
  toy(T &&t):T(std::move(t)) {}
  int state = 0;
};

template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }

l' sizeof(make_toy( []{std::cout << "hello world!\n"; } )) est sizeof(int) (ainsi, le ci-dessus est illégal parce que vous ne pouvez pas créer un lambda dans un non-évalué contexte: vous devez créer un nom d' auto toy = make_toy(blah); alors n' sizeof(blah), mais c'est juste du bruit). sizeof([]{std::cout << "hello world!\n"; }) encore 1 (qualifications similaires).

Si nous créons un autre jouet de type:

template<class T>
struct toy2:T {
  toy2(toy2 const&)=default;
  toy2(T const&t):T(t), t2(t) {}
  T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }

cela a deux copies de la lambda. Comme ils ne peuvent pas partager la même adresse, sizeof(toy2(some_lambda)) est 2!

53voto

Sam Varshavchik Points 2563

Un lambda n'est pas un pointeur de fonction.

Un lambda est une instance d'une classe. Votre code est à peu près équivalente à:

class f_lambda {
public:

  auto operator() { return 17; }
};

f_lambda f;
std::cout << f() << std::endl;
std::cout << &f << std::endl;
std::cout << sizeof(f) << std::endl;

La classe interne qui représente un lambda n'a pas de membres de la classe, d'où son sizeof() est de 1 (il ne peut pas être 0, pour des raisons suffisamment indiqué ailleurs).

Si votre lambda ont été pour la capture de certaines variables, ils vont être l'équivalent de membres de la classe, et votre sizeof() indiquent en conséquence.

27voto

ComicSansMS Points 12749

Votre compilateur plus ou moins traduit le lambda à la suite de type struct:

struct _SomeInternalName {
    int operator()() { return 17; }
};

int main()
{
     _SomeInternalName f;
     std::cout << f() << std::endl;
}

Depuis cette structure n'a pas les membres non statiques, il a la même taille comme un vide struct, qui est - 1.

Que les changements dès que vous ajoutez un non-vide de capturer la liste de vos lambda:

int i = 42;
auto f = [i]() { return i; };

Ce qui se traduira par

struct _SomeInternalName {
    int i;
    _SomeInternalName(int outer_i) : i(outer_i) {}
    int operator()() { return i; }
};


int main()
{
     int i = 42;
     _SomeInternalName f(i);
     std::cout << f() << std::endl;
}

Depuis le générés struct a maintenant besoin de stocker un non-statique int membre pour la capture, sa taille va croître sizeof(int). La taille va continuer de croître que vous capturez plus de choses.

(Veuillez prendre le struct analogie avec un grain de sel. Alors que c'est une belle façon de raisonner sur la façon dont les lambdas de travail en interne, ce n'est pas une traduction littérale de ce que le compilateur va le faire)

13voto

legends2k Points 6380

Ne pas le lambda, à mimumum, un pointeur vers sa mise en œuvre?

Pas nécessairement. Selon la norme, la taille de l'unique, sans nom de la classe est mise en œuvre définies. Extrait de [expr.prim.lambda], C++14 (l'emphase est mienne):

Le type de la lambda-expression (qui est aussi le type de la fermeture de l'objet) est un numéro unique, sans nom pseudarthrose type de classe - disant de la clôture de type dont les propriétés sont décrites ci-dessous.

[ ... ]

Une mise en œuvre peut définir la fermeture type différemment de ce qui est décrit ci-dessous à condition que cela ne modifie pas le comportement observable du programme autre que par l'évolution de:

- la taille et/ou l'alignement de la fermeture type,

- si la fermeture est de type trivialement copiable (Clause 9),

- si la fermeture est de type standard-classe de mise en page (Clause 9), ou

- si le type de fermeture est un module de classe (article 9)

Dans votre cas, pour le compilateur que vous utilisez -- vous obtenez une taille de 1, ce qui ne veut pas dire qu'il est fixe. Elle peut varier entre les différents compilateur implémentations.

8voto

GeorgeAl Points 399

À partir de http://en.cppreference.com/w/cpp/language/lambda:

L'expression lambda construit un sans nom prvalue objet temporaire de unique sans nom non-union de non-agrégation type de classe, connu comme une fermeture de type, qui est déclarée (pour les fins de l'ADL) dans le plus petit bloc de portée, l'étendue de classe, ou de l'espace de champ qui contient l'expression lambda.

Si le lambda-expression capte quoi que ce soit par copie (implicitement avec la capture de la clause [=] ou explicitement à une capture qui ne comprennent pas le caractère &, par exemple, [a, b, c]), la fermeture type comprend sans nom non-membres de données statiques, a déclaré dans un ordre non spécifié, qui contiennent des copies de toutes les entités qui ont été si bien capturé.

Pour les entités qui sont capturés par référence (avec la valeur par défaut de capture [&] ou lorsque vous utilisez le caractère &, par exemple, [&a, &b, &c]), il est non spécifié si d'autres membres de données sont déclarées à la fermeture type

À partir de http://en.cppreference.com/w/cpp/language/sizeof

Lorsqu'il est appliqué à une classe vide type, renvoie toujours 1.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X