2011-03-26 1 views
16

consideri il codice seguente:Qualora un blocco letterale Retain riferimento blocchi di heap allocati

// t included so block1 is a stack block. See [1] below 
int t = 1; 
SimpleBlock block1 = ^{ NSLog(@"block1, %d", t); }; 

// copy block1 to the heap 
SimpleBlock block1_copied = [block1 copy]; 

// block2 is allocated on the stack, and refers to 
// block1 on the stack and block1_copied on the heap 
SimpleBlock block2 = ^{ 
    NSLog(@"block2"); 
    block1_copied(); 
    block1(); 
}; 
[block1_copied release]; 

// When the next line of code is executed, block2_copied is 
// allocated at the same memory address on on the heap as 
// block1_copied, indicating that block1_copied has been 
// deallocated. Why didn't block2 retain block1_copied? 

SimpleBlock block2_copied = [block2 copy]; 
block2_copied(); 
[block2_copied release]; 

Qualora, per completezza, SimpleBlock è definita da:

typedef void (^SimpleBlock)(void); 

Come indicato dal commento nel codice , i miei test (usando sia GCC 4.2 che LLVM 2.0) mostrano che block1_copied è deallocato dal momento in cui viene chiamata [block2 copy], ma secondo la documentazione che ho letto [1,3], i blocchi sono oggetti e blocchi obiettivo-c conservare gli oggetti obiettivo-c a cui si riferiscono [2] (nella variabile non di istanza c ase).

Inoltre, quando il blocco2 viene copiato, il suo riferimento al blocco1 viene anche modificato in un riferimento a una nuova copia del blocco1 (che è diverso da block1_copied), come previsto, poiché i blocchi copiano tutti i blocchi a cui fanno riferimento [ 2].

Quindi, cosa sta succedendo qui?

A) Se i blocchi conservano oggetti obiettivo-c a cui fanno riferimento e i blocchi sono oggetti obiettivo-c, perché viene bloccato deal1_copied prima che il blocco2 esca dall'ambito?

B) Se i blocchi copiano blocchi a cui fanno riferimento, e se l'invio - (id) copia su un blocco con allocazione heap in realtà incrementa semplicemente il conteggio di mantenimento, perché viene bloccato deal1_copied prima che block2 esca dall'ambito?

C) Se questo è il comportamento previsto, dove è la documentazione che lo spiega?

[1] http://cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html
[2] http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html
[3] http://clang.llvm.org/docs/BlockLanguageSpec.txt

Nota: Nel mio test, il risultato dell'esecuzione di questo codice è una chiamata infinitamente ricorsiva a block2_copied(), dal momento che block1_copied() aveva lo stesso indirizzo di memoria di block2_copied.

+1

+1 interessante .... –

risposta

5

This is the specification. In questo momento è leggermente stantio e non ha il formalismo di una specifica normale. Tuttavia, i Blocchi sono stati proposti nel gruppo di lavoro C e in tale contesto è stata discussa una specifica più formale.

In particolare, le specifiche dice:

L'operatore Block_copy mantiene tutte oggetti tenuti nelle variabili di memorizzazione automatica si fa riferimento all'interno del blocco espressione (o formare i riferimenti forti se in esecuzione sotto la raccolta dei rifiuti). Si presuppone che le variabili oggetto del tipo __block storage contengano i normali puntatori senza alcuna clausola per conservare e rilasciare i messaggi.

Così, il comportamento che si sta vedendo è corretto, anche se è sicuramente un trabocchetto!

Un blocco non manterrà nulla finché il blocco non viene copiato. Come i blocchi che iniziano in pila, questa è in gran parte una decisione basata sulle prestazioni.

Se si dovesse modificare il codice per:

SimpleBlock block2_copied = [block2 copy]; 
[block1_copied release]; 

si comporta come previsto.

L'analizzatore statico dovrebbe rilevare ciò, ma non lo fa (per favore, file a bug).

+0

Grazie per la risposta. Ho appena presentato un errore sull'analizzatore statico. Penso che questa sia anche un'accuratezza della documentazione dello sviluppatore, dato il testo da "Argomenti Programmazione Blocchi"> "Blocchi e Variabili"> "Variabili Oggetto e Blocco"> "Oggetti Objective-C". Nessuno del testo qui indica che il mantenimento avviene solo dopo che il blocco è stato copiato. Presenterò un secondo bug sulla documentazione degli sviluppatori. –

1

Ho notato che lo stesso sembra accadere con oggetti normali. Questo codice:

NSNumber *foo = [[NSNumber alloc] initWithInt:42]; 
void(^block)(void) = ^{ NSLog(@"foo = %@", foo); }; 
[foo release]; 
NSNumber *foo2 = [[NSNumber alloc] initWithInt:43]; 
void(^block_copy)(void) = [block copy]; 
block_copy(); 

Stampe "foo = 43"

E forza essere un comportamento previsto. Per citare la documentazione di Apple:

Quando si copia un blocco, tutti i riferimenti ad altri isolati da all'interno di tale blocco vengono copiati se necessario

Al punto block1_copy viene rilasciato, block2 non è stato ancora copiato .

+0

Questo è assolutamente vero. Quindi questo potrebbe essere il comportamento previsto. Il riferimento [2] dice anche "In un ambiente conteggiato di riferimento, per impostazione predefinita quando si fa riferimento a un oggetto Objective-C all'interno di un blocco, viene mantenuto. Ciò è vero anche se si fa semplicemente riferimento a una variabile di istanza dell'oggetto. con il modificatore del tipo di memoria '__block', tuttavia, non vengono mantenuti." Questo non dice nulla sulla necessità di copiare, anche se potrebbe essere dato a questi esempi. In seguito menzionano che i _blocks_ di riferimento vengono copiati se necessario. Questi sembrano potrebbero essere problemi diversi però. –

+0

@AndrewHershberger: vero. Il grosso problema è che non esiste una specifica reale. – Anomie

+0

@anomie, in questo frammento non ci si riferisce a foo2, ma si afferma che l'output è il valore di foo2. Com'è possibile? –