2011-01-18 22 views
9

Estoy tratando de reducir un error a un caso reproducible mínimo y encontré algo extraño.¿Por qué no se cuelga?

consideran este código:

static NSString *staticString = nil; 
int main (int argc, const char * argv[]) { 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    if (staticString == nil) { 
     staticString = [[NSArray arrayWithObjects:@"1", @"2", @"3", nil] componentsJoinedByString:@","]; 
    } 

    [pool drain]; 

    NSLog(@"static: %@", staticString); 
    return 0; 
} 

estoy esperando este código se bloquee. En su lugar, registra:

2011-01-18 14:41:06.311 EmptyFoundation[61419:a0f] static: static: 

Sin embargo, si cambio el NSLog() a:

NSLog(@"static: %s", [staticString UTF8String]); 

Entonces se hace accidente.

edición un poco más de información:

Después de vaciar la piscina:

NSLog(@"static: %@", staticString); //this logs "static: static: " 
NSLog(@"static: %@", [staticString description]); //this crashes 

Así que al parecer la invocación de un método en la cadena es lo suficientemente bueno para conseguir que se bloquee. En ese caso, ¿por qué el registro de la cadena no causa directamente que se bloquee? ¿No debería NSLog() invocar el método -description?

¿De dónde viene el segundo "estático:"? ¿Por qué no se está cayendo?


Resultados:

Tanto Kevin Ballard y Graham Lee son correctos. Graham está en lo cierto al darse cuenta de que NSLog() es y no invocando -description (como estaba asumiendo erróneamente), y Kevin está casi seguro de que este es un problema extraño relacionado con la pila al copiar una cadena de formato y un va_list.

  1. NSLogging y NSString no invoca -description. Graham mostró esto con elegancia, y si rastreas a través de las fuentes de Core Foundation que hacen el registro, verás que este es el caso. Cualquier traza inversa originada dentro de NSLog muestra que invoca NSLogv =>_CFLogvEx =>_CFStringCreateWithFormatAndArgumentsAux =>_CFStringAppendFormatAndArgumentsAux. _CFStringAppendFormatAndArgumentsAux() (línea 5365) es donde se desarrolla toda la magia. Puede ver que está pasando por el poder para encontrar todas las sustituciones %. Solo termina invocando la función de copia de descripción si el tipo de la sustitución es CFFormatObjectType, la función de descripción no es nula y la sustitución no ha sido manejada ya por otro tipo. Como hemos demostrado que la descripción no se está copiando, es razonable suponer que un NSString se maneja antes (en cuyo caso probablemente se trate de una copia de bytes sin formato), lo que nos lleva a creer ...
  2. Aquí hay un error de acumulación, como Kevin conjetura. De alguna manera, el puntero que era apuntando a la cadena autorretratada se sustituye por un objeto diferente, que sucede como NSString. Por lo tanto, no se cuelga. Extraño. Sin embargo, si cambiamos el tipo de la variable estática a otra cosa, como un NSArray, se llama al método -description y el programa se bloquea como se esperaba.

Qué tan verdaderamente extraño. Los puntos van a Kevin por ser el más correcto sobre la raíz causa del comportamiento, y felicitaciones a Graham por corregir mi pensamiento erróneo. Desearía poder aceptar dos respuestas ...

+0

¿Por qué esperas que se cuelgue? – Davidann

+0

'NSLog ([cadena NSStringWithFormat: @" static:% @ ", staticString])' __doses__ causa un bloqueo, por lo que es realmente debido a un comportamiento diferente de 'NSLog' al manejar'% @ ' – Joost

+0

Nitpicks: creo que su análisis wrt/"Results:" & '_CFStringAppendFormatAndArgumentsAux' es incorrecto. Para cada '% @', '_CFStringAppendFormatAndArgumentsAux' intentará 1) una función' copyDesc' que se pasó a través de un argumento, 2) '__CFCopyFormattingDescription', que para los objetos ObjC resulta en intentar' _copyFormattingDescription: ', y finalmente 3)' CFCopyDescription', que para los objetos ObjC da como resultado '_copyDescription', y el desmontaje muestra 'NSObject' defaults a llamar' -description'. Por lo tanto, el 99% del tiempo, '% @' dará como resultado que se llame a '-description'. – johne

Respuesta

9

Mi mejor estimación para lo que está viendo es que NSLog() copia la cadena de formato (probablemente como una copia mutable), y luego analiza los argumentos. Como ha tramitado staticString, sucede que la copia de la cadena de formato se está colocando en la misma ubicación. Esto hace que vea el resultado "static: static: " que describió. Por supuesto, este comportamiento no está definido: no hay garantía de que siempre use la misma ubicación de memoria para esto.

Por otro lado, su NSLog(@"static: %s", [staticString UTF8String]) está accediendo a staticString antes de que ocurra la copia de cadena de formato, lo que significa que está accediendo a la memoria basura.

+0

Dudo que copie la cadena, pero probablemente '-getCString:' o similar para que pueda usar funciones de estilo 'printf' en el contenido. Pueden ser esos caracteres que ves. –

+0

@Graham en realidad, es una implementación personalizada de 'printf()'. Estaré editando mi pregunta con lo que he encontrado. –

+0

OK, acabo de pasar por 'NSLog()', llama a 'CFStringCreateWithFormatAndArgumentsAux()' que construye un nuevo 'CFMutableString'. De hecho, eso me hace pensar que no necesita copiar la cadena porque no está haciendo modificaciones in situ. En otras palabras, no está haciendo 'vsprintf()' en la cadena de formato, que era lo que originalmente esperaba. –

1

El acceso a la memoria deslocalizada no necesariamente causa un bloqueo. El comportamiento es undefined. ¡Estás esperando demasiado!

+1

Si coloco un registro antes de drenar el grupo, se registra como se esperaba. Cuando agoto, espero que 'NSString' sea desasignado, pero el puntero estático permanezca igual. Por lo tanto, intentar enviar el método '-description' para iniciar sesión debería dar como resultado un' EXC_BAD_ACCESS' debido al acceso a la memoria desasignada. En cambio, está registrando algo más. Sin embargo, invocar explícitamente un método en el objeto hace que se bloquee. –

+1

Un error EXC_BAD_ACCESS solo ocurrirá si la página VM donde se encuentra esa memoria ya no es válida. La desasignación del objeto solo marca su espacio como libre en el seguimiento interno de malloc, pero otras partes de la página de VM todavía pueden ser utilizadas por otros bloques de memoria. Entonces, acceder a esa memoria después no causará un colapso a menos que se libere todo lo demás en esa página, y malloc devuelve la página al kernel para su reutilización. –

1

Quizás tiene algo que ver con el @ "static:" que se almacena en la misma ubicación de memoria que staticString. staticString será desasignado y almacena @ "static:% @" en esa ubicación de memoria reciclada, entonces el puntero staticString está en "static:% @" por lo que termina estático: static :.

+1

'@" static:% @ "' se almacena en la parte TEXTO del binario, ya que es una cadena constante. –

8

Su suposición de que NSLog() llama a en una instancia de NSString es incorrecta. Acabo de añadir esta categoría:

@implementation NSString (GLDescription) 

- (NSString *)description { 
    NSLog(@"-description called on %@", self); 
    return self; 
} 

@end 

no causa un desbordamiento de pila, debido a que no se consiga llamar de forma recursiva. No sólo eso, sino que si inserto esa categoría en el código en su pregunta, creo que esta salida:

2011-01-18 23:04:11.653 LogString[3769:a0f] -description called on 1 
2011-01-18 23:04:11.656 LogString[3769:a0f] -description called on 2 
2011-01-18 23:04:11.657 LogString[3769:a0f] -description called on 3 
2011-01-18 23:04:11.658 LogString[3769:a0f] static: static: 

así llegamos a la conclusión de que NSLog() no llama -description en una NSString que viene a través de sus argumentos. La razón por la que obtienes la cadena estática dos veces es una peculiaridad de los datos en la pila cuando accedes erróneamente a la variable staticString liberada.

+0

Esto no está realmente relacionado con la pila. Vea mi respuesta para mi mejor estimación de lo que está sucediendo, pero en resumen, NSLog muy probablemente copie su cadena de formato, y la cadena copiada termina en la misma ubicación de memoria que el 'staticString' original. –

+0

En cuanto a '-description', es posible que, para' NSString', en realidad esté retrocediendo al comportamiento 'CFCopyDescription()'. O podría ser inteligente sobre cadenas, es difícil saberlo con certeza. –

+2

+1 por probar que estoy equivocado. :) –

1

Este es un caso de "Uso después de free()". Lo que sucede es un "comportamiento indefinido". Su ejemplo es realmente diferente de:

char *stringPtr = NULL; 
stringPtr = malloc(1024); // Example code, assumes this returns non-NULL. 
strcpy(stringPtr, "Zippers!"); 
free(stringPtr); 
printf("Pants: %s\n", stringPtr); 

¿Qué ocurre en la línea de printf? Quién sabe. Cualquier cosa desde Pants: Zippers! hasta Pants: (...garbage...) Core Dump.

Todas las cosas específicas de Objective-C son realmente irrelevantes, es el hecho de que estás usando un puntero a la memoria que ya no es válido es lo único que importa. Será mejor que arrojes dardos a la pared que tratar de explicar "por qué" y no se cuelgue e imprima static: static. Por razones de rendimiento, la mayoría de las implementaciones de malloc no molestan en "cosechar" free() 'd asignaciones hasta que tengan que hacerlo. En mi humilde opinión, esta es probablemente la razón por la que su ejemplo no está fallando en la forma en que lo esperaba.

Si realmente quiere ver este accidente programa en particular, puede realizar una de las siguientes opciones:

  • Establecer la variable de entorno CFZombieLevel-17 (+ garabato no lo hacen gratis).
  • Establezca la variable de entorno NSZombieEnabled en YES.
  • Establezca la variable de entorno DYLD_INSERT_LIBRARIES en /usr/lib/libgmalloc.dylib (consulte man libgmalloc).
+1

Esperaba que fallara porque suponía que el método '-description' se invocaría en la cadena. Invocar un método en un objeto desasignado * provocará * un bloqueo en Objective-C. Sin embargo, resulta que esto no es lo que está sucediendo: las cadenas tienen una función especial a través del sistema de registro. Sin esa suposición, todo el argumento del "comportamiento indefinido" tiene mucho más sentido. –

+0

@Dave Confía en mí, no tiene nada que ver con "las cadenas tienen una función especial a través del sistema de registro". Esta es una correlación, no una causa. Pruébelo con esta línea: 'staticString = [NSString stringWithString: [[NSArray arrayWithObjects: @" 1 ", @" 2 ", @" 3 ", nil] componentsJoinedByString: @", "]];'. – johne

+0

@Dave ... y este pasa a "funcionar": 'staticString = [NSString stringWithFormat: @"% @ ", [NSArray arrayWithObjects: @" 1 ", @" 2 ", @" 3 ", nil]] ; ' – johne

Cuestiones relacionadas