Ich möchte dies etwas näher erläutern und eine vollständigere Antwort geben. Betrachten wir zunächst diesen Code:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
void (^block)() = nil;
block();
}
Wenn Sie dies ausführen, sehen Sie einen Absturz in der block()
Zeile, der ungefähr so aussieht (wenn Sie auf einer 32-Bit-Architektur ausgeführt werden - das ist wichtig):
EXC_BAD_ACCESS (Code = 2, Adresse = 0xc)
Warum ist das so? Nun, das 0xc
ist das Wichtigste. Der Absturz bedeutet, dass der Prozessor versucht hat, die Informationen an der Speicheradresse zu lesen 0xc
. Dies ist fast definitiv eine völlig falsche Sache. Es ist unwahrscheinlich, dass dort etwas ist. Aber warum hat es versucht, diesen Speicherort zu lesen? Nun, es liegt an der Art und Weise, wie ein Block tatsächlich unter der Haube aufgebaut ist.
Wenn ein Block definiert ist, erstellt der Compiler tatsächlich eine Struktur auf dem Stapel in der folgenden Form:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
};
Der Block ist dann ein Zeiger auf diese Struktur. Das vierte Mitglied invoke
dieser Struktur ist das interessante. Es ist ein Funktionszeiger, der auf den Code zeigt, in dem die Implementierung des Blocks gespeichert ist. Der Prozessor versucht also, zu diesem Code zu springen, wenn ein Block aufgerufen wird. Beachten Sie, dass, wenn Sie die Anzahl der Bytes in der Struktur vor dem invoke
Element zählen, Sie feststellen, dass 12 in Dezimalzahl oder C in Hexadezimalzahl vorhanden sind.
Wenn also ein Block aufgerufen wird, nimmt der Prozessor die Adresse des Blocks, addiert 12 und versucht, den an dieser Speicheradresse gespeicherten Wert zu laden. Es wird dann versucht, zu dieser Adresse zu springen. Wenn der Block jedoch Null ist, wird versucht, die Adresse zu lesen 0xc
. Dies ist eindeutig eine Duff-Adresse, und so erhalten wir den Segmentierungsfehler.
Der Grund, warum es ein Absturz wie dieser sein muss, anstatt stillschweigend zu scheitern, wie es ein Objective-C-Nachrichtenaufruf tut, ist wirklich eine Entwurfsentscheidung. Da der Compiler entscheidet, wie der Block aufgerufen werden soll, muss er überall dort, wo ein Block aufgerufen wird, keinen Prüfcode einfügen. Dies würde die Codegröße erhöhen und zu einer schlechten Leistung führen. Eine andere Möglichkeit wäre die Verwendung eines Trampolins, das die Nullprüfung durchführt. Dies würde jedoch auch zu Leistungseinbußen führen. Objective-C-Nachrichten durchlaufen bereits ein Trampolin, da sie die Methode aufrufen müssen, die tatsächlich aufgerufen wird. Die Laufzeit ermöglicht das verzögerte Einfügen von Methoden und das Ändern von Methodenimplementierungen, sodass sie ohnehin schon ein Trampolin durchläuft. Die zusätzliche Strafe für die Nullprüfung ist in diesem Fall nicht signifikant.
Ich hoffe, das hilft ein wenig, die Gründe zu erklären.
Weitere Informationen finden Sie in meinen Blog- Beiträgen .
Vorsichtsmaßnahme: Ich bin kein Experte für Blöcke.
Blöcke sind Objective-C-Objekte, aber das Aufrufen eines Blocks ist keine Nachricht , obwohl Sie immer noch versuchen könnten,
[block retain]
einennil
Block oder andere Nachrichten zu erstellen.Hoffentlich hilft das (und die Links).
quelle
__block
Typ eine Kategorie hinzufügen ... aber ich bin mir nicht sicher.#define nilBlock ^{}
kann auch Ihr Leben leichter machen.nilBlock
Ansatz nachgedacht , leider stört die Eingabe - das Erstellen eines separaten Nullwerts für jeden Blocktyp macht nicht viel Spaß.[block call]
die eine interne Prüfung durchführt, kann hilfreich sein. Nicht sicher, wie nahe Blöcke an ObjC-Objekten sind.Dies ist meine einfach schönste Lösung… Vielleicht gibt es eine Möglichkeit, eine universelle Ausführungsfunktion mit diesen c var-args zu schreiben, aber ich weiß nicht, wie ich das schreiben soll.
void run(void (^block)()) { if (block)block(); } void runWith(void (^block)(id), id value) { if (block)block(value); }
quelle