J'aimerais expliquer un peu plus, avec une réponse plus complète. Commençons d'abord par examiner ce code:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
void (^block)() = nil;
block();
}
Si vous exécutez ce code, vous verrez un crash sur l' block()
ligne qui ressemble à quelque chose comme ceci (en cas d'exécution sur une architecture 32 bits, ça c'est important):
EXC_BAD_ACCESS (code=2, address=0xc)
Alors, pourquoi est-ce? Eh bien, l' 0xc
est la plus importante. Le crash signifie que le processeur a essayé de lire les informations à l'adresse de mémoire 0xc
. Ce n'est presque certainement tout à fait tort chose à faire. Il est peu probable, il n'y a rien là. Mais pourquoi avait-il essayer de lire cet emplacement de la mémoire? Eh bien, c'est en raison de la façon dont le bloc est en fait construit sous le capot.
Quand un bloc est défini, le compilateur crée en fait une structure sur la pile, de cette forme:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
Le bloc est alors un pointeur vers cette structure. Le quatrième membre, invoke
, de cet ouvrage est intéressant. Il est un pointeur de fonction, pointant vers le code où le bloc de mise en œuvre est tenu. De sorte que le processeur tente d'accéder à ce code lorsqu'un bloc est invoquée. Notez que si vous comptez le nombre d'octets dans la structure avant de l' invoke
membre, vous trouverez qu'il ya 12 en décimal, ou C en hexadécimal.
Alors, quand un bloc est invoquée, le compilateur prend l'adresse du bloc, ajoute 12 et essaie de charger la valeur détenus à cette adresse mémoire. Puis, il tente de sauter à cette adresse. Mais si le bloc est nul, donc il va essayer de lire l'adresse 0xc
. C'est une duff adresse, clairement, et nous obtenons donc l'erreur de segmentation.
Maintenant, la raison pour laquelle il doit être un crash comme ça plutôt que d'en silence échouer comme un Objectif-C message d'appel n'est vraiment un choix de conception. Puisque le compilateur fait le travail de décider de la façon d'invoquer le bloc, il faudrait injecter néant code de vérification partout un bloc est invoquée. Cela permettrait d'accroître la taille du code et conduire à de mauvaises performances. Une autre option serait d'utiliser un trampoline qui fait le néant de la vérification. Toutefois cela permettrait également d'engager des performances. Objective-C messages déjà passer par un trampoline depuis qu'ils ont besoin de regarder pour la méthode qui sera effectivement appelée. Le runtime permet pour les paresseux injection de méthodes et de l'évolution des implémentations de méthode, il est donc déjà en cours par le biais d'un trampoline, de toute façon. La sanction de faire le néant, la vérification n'est pas significative dans ce cas.
J'espère que ça aide un peu pour lui expliquer les raisons.
Pour plus d'informations, voir mon blog posts.