74 votes

Pourquoi ne nul / NULLE blocs de provoquer des erreurs de bus lors de son exécution?

J'adore les "blocs" sont ajoutés les versions récentes d'Objective-C. j'ai commencé à l'aide de blocs beaucoup et vite remarqué que nul blocs de provoquer des erreurs de bus:

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

Cela semble aller à l'encontre du comportement habituel de l'Objective-C, qui ignore les messages à néant les objets:

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

Donc j'ai du avoir recours à l'habitude néant vérifier avant j'utiliser un bloc:

if (aBlock != nil)
    aBlock();

Ou utiliser mannequin blocs:

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

Est-il une autre option? Est-il une raison pourquoi néant blocs ne pouvait pas être simplement un nop?

150voto

mattjgalloway Points 24217

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.

40voto

hfossli Points 6815

Définir une macro comme ceci

#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 souhaitez obtenir la valeur de retour de la bloquer et vous n'êtes pas sûr si le bloc existe ou pas, alors vous êtes probablement mieux de simplement taper:

block ? block() : nil;

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

9voto

Stephen Furlani Points 3210

Mise en garde: je ne suis pas expert dans les Blocs.

Les blocs sont en objective-c des objets , mais l'appel d'un bloc n'est pas un message, mais vous pouvez toujours essayer [block retain]ing nil bloc ou d'autres messages.

J'espère que c' (et les liens) aide.

2voto

Rene Dohan Points 766

C'est ma plus belle solution... Peut-être qu'il est possible d'écrire un universel de la fonction d'exécution avec ces c var-args mais je ne sais pas comment écrire ça.

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

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

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