2010-11-10 17 views
67

j'ai commencé à l'aide de blocs beaucoup et rapidement remarqué que les blocs nil provoquent des erreurs de bus:Pourquoi les blocs nil/NULL provoquent-ils des erreurs de bus lors de l'exécution?

typedef void (^SimpleBlock)(void); 
SimpleBlock aBlock = nil; 
aBlock(); // bus error 

Cela semble aller à l'encontre du comportement habituel de l'Objective-C qui ne tient pas compte des messages aux objets nil:

NSArray *foo = nil; 
NSLog(@"%i", [foo count]); // runs fine 

donc je dois recourir à l'habitude nul vérifier avant que j'utiliser un bloc:

if (aBlock != nil) 
    aBlock(); 

Ou utiliser des blocs factices:

aBlock = ^{}; 
aBlock(); // runs fine 

Existe-t-il une autre option? Y a-t-il une raison pour laquelle des blocs nuls ne pourraient pas être simplement un nop? Mise en garde: Je ne suis pas un expert en matière de blocs.

Répondre

134

Je voudrais expliquer cela un peu plus, avec une réponse plus complète. D'abord, examinons ce code:

#import <Foundation/Foundation.h> 
int main(int argc, char *argv[]) {  
    void (^block)() = nil; 
    block(); 
} 

Si vous exécutez cela, alors vous verrez un accident sur la block() ligne qui ressemble à quelque chose comme ça (lorsqu'il est exécuté sur une architecture 32 bits - ce qui est important):

EXC_BAD_ACCESS (code = 2, adresse = 0xc)

Alors, pourquoi est-ce? Eh bien, le 0xc est le bit le plus important.Le crash signifie que le processeur a essayé de lire les informations à l'adresse mémoire 0xc. C'est presque définitivement une chose complètement incorrecte à faire. Il est peu probable qu'il y ait quelque chose là-bas. Mais pourquoi a-t-il essayé de lire cet emplacement de mémoire? Eh bien, c'est en raison de la façon dont un bloc est réellement construit sous le capot.

Lorsqu'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 cette structure est l'intéressant. C'est un pointeur de fonction, pointant vers le code où l'implémentation du bloc est tenue. Ainsi, le processeur essaie de sauter à ce code lorsqu'un bloc est appelé. Notez que si vous comptez le nombre d'octets dans la structure avant le membre invoke, vous constaterez qu'il existe 12 en décimal, ou C en hexadécimal.

Ainsi, lorsqu'un bloc est appelé, le processeur prend l'adresse du bloc, ajoute 12 et essaie de charger la valeur contenue à cette adresse mémoire. Il essaie ensuite de sauter à cette adresse. Mais si le bloc est nul alors il va essayer de lire l'adresse 0xc. Ceci est une adresse duff, clairement, et ainsi nous obtenons la faute de segmentation. Maintenant, la raison pour laquelle il doit s'agir d'un crash comme celui-ci plutôt que d'échouer silencieusement comme un appel de message Objective-C est vraiment un choix de conception. Puisque le compilateur fait le travail de décider comment invoquer le bloc, il devrait injecter le code de vérification nul partout où un bloc est invoqué. Cela augmenterait la taille du code et conduirait à de mauvaises performances. Une autre option serait d'utiliser un trampoline qui fait la vérification nulle. Cependant, cela entraînerait également une pénalité de performance. Les messages Objective-C passent déjà par un trampoline car ils doivent rechercher la méthode qui sera réellement appelée. L'exécution permet l'injection paresseuse des méthodes et le changement des implémentations de la méthode, de sorte qu'elle passe déjà par un trampoline. La pénalité supplémentaire de faire la vérification nulle n'est pas significative dans ce cas. J'espère que cela aide un peu à expliquer la logique. Pour plus d'informations, voir mon blogposts.

8

blocs sontobjective-c objects mais appeler un block is not a message, même si vous pouvez toujours essayer un bloc [block retain] ing nil ou d'autres messages.

Espérons que cela (et les liens) aide.

+0

Merci, liens intéressants. Je sais que l'appel d'un bloc n'est pas la même chose que l'envoi d'un message, mais conceptuellement ce serait bien si les blocs nuls étaient aussi indulgents que les objets nuls. – zoul

+0

Vous pouvez ajouter une catégorie au type '__block' ... mais je ne suis pas sûr. '#define nilBlock^{}' peut aussi vous faciliter la vie. –

+0

Je pensais à l'approche 'nilBlock', malheureusement le typage entrave la création - créer une valeur nulle différente pour chaque type de bloc n'est pas très amusant. – zoul

2

Ceci est ma solution la plus simple ... Peut-être qu'il est possible d'écrire une fonction d'exécution universelle avec ces c-var-arguments, mais je ne sais pas comment écrire cela.

void run(void (^block)()) { 
    if (block)block(); 
} 

void runWith(void (^block)(id), id value) { 
    if (block)block(value); 
} 
38

La réponse de Matt Galloway est parfaite! Bonne lecture!

Je veux juste ajouter qu'il y a des façons de rendre la vie plus facile. Vous pouvez définir une macro comme celle-ci:

#define BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__) : nil 

Il peut prendre 0 - n arguments. Exemple d'utilisation

typedef void (^SimpleBlock)(void); 
SimpleBlock simpleNilBlock = nil; 
SimpleBlock simpleLogBlock = ^{ NSLog(@"working"); }; 
BLOCK_SAFE_RUN(simpleNilBlock); 
BLOCK_SAFE_RUN(simpleLogBlock); 

typedef void (^BlockWithArguments)(BOOL arg1, NSString *arg2); 
BlockWithArguments argumentsNilBlock = nil; 
BlockWithArguments argumentsLogBlock = ^(BOOL arg1, NSString *arg2) { NSLog(@"%@", arg2); }; 
BLOCK_SAFE_RUN(argumentsNilBlock, YES, @"ok"); 
BLOCK_SAFE_RUN(argumentsLogBlock, YES, @"ok"); 

Si vous voulez obtenir la valeur de retour du bloc et vous n'êtes pas sûr si le bloc existe ou non alors vous êtes probablement mieux juste taper:

block ? block() : nil; 

De cette façon, vous pouvez facilement définir la valeur de repli. Dans mon exemple "néant".

+1

__VA_ARGS__ fait des problèmes dans les fichiers .mm – BergP

+0

@PavelKatunin Je n'ai jamais testé ça – hfossli