2009-08-04 4 views
2

J'ai récemment perdu environ une demi-heure traquer ce comportement bizarre dans NSLog (...):NSLog (...) spécificateur de format incorrect affecte d'autres variables?

NSString *text = @"abc"; 
long long num = 123; 
NSLog(@"num=%lld, text=%@",num,text); //(A) 
NSLog(@"num=%d, text=%@",num,text); //(B) 

ligne (A) affiche l'attendu "num = 123, text = abc", mais la ligne (B) affiche "num = 123, text = (null)".

De toute évidence, l'impression d'un long long avec %d est une erreur, mais quelqu'un peut-il expliquer pourquoi cela provoquerait text d'être imprimé comme nul?

+0

Si vous compilez avec l'option -Wall, le compilateur vous avertira de problèmes de ce type; Je recommande également fortement le -Werror ainsi les avertissements cassent toujours la construction. –

+1

@Adam Rosenfield, juste une note que la prise en charge de la vérification de format, ala '-Wformat', a toujours été un peu douteuse dans gcc/objc. Cela semble aller mieux avec les versions ultérieures du compilateur, mais je viens de faire une vérification rapide sous Xcode 3.1 et il n'a pas attrapé l'erreur ci-dessus. – johne

+0

Il n'attrape pas l'erreur car -Wformat ne fonctionne que sur les chaînes C (comme dans printf) et est complètement incapable d'analyser les constantes d'objet NSString * (que NSLog utilise). –

Répondre

9

Vous venez de gâcher l'alignement de la mémoire sur votre pile. Je suppose que vous utilisez le dernier produit Apple avec processeur x86. Compte tenu de ces hypothèses en compte votre pile ressemble que dans les deux situations:

 
    |  stack   | first | second | 
    +---------------------+-------+--------+ 
    |  123   |  | %d | 
    +---------------------+ %lld +--------+ 
    |   0   |  | %@ | 
    +---------------------+-------+--------+ 
    | pointer to text | %@ |ignored | 
    +---------------------+-------+--------+ 

Dans la première situation que vous mettez sur la pile 8 octets puis 4 octets. Et NSLog est chargé de reprendre de la pile 12 octets (8 octets pour %lld et 4 octets pour %@). Dans la deuxième situation, vous demandez à NSLog de commencer par prendre 4 octets (%d). Puisque votre variable a une longueur de 8 octets et contient un nombre vraiment petit, ses 4 octets supérieurs seront 0. Ensuite, lorsque NSLog essaiera d'imprimer du texte, cela prendra nil de la pile.

Depuis envoi de message à nil est valide dans Obj-C NSLog enverra simplement description: à nil obtenir probablement rien, puis imprimer (null). En fin de compte depuis Objective-C est juste C avec des ajouts, l'appelant nettoie tout ce gâchis.

1

La façon dont les varargs sont implémentés dépend du système. Mais ce qui est susceptible de se produire est que les arguments sont stockés de manière consécutive dans un tampon, même si les arguments peuvent être de tailles différentes. Donc les 8 premiers octets (en supposant que la taille d'un long long int) des arguments est le long long int, et les 4 octets suivants (en supposant que c'est la taille d'un pointeur sur votre système) est le pointeur NSString.

Puis, quand vous dites la fonction qu'elle attend un int puis un pointeur, il attendre les 4 premiers octets à l'int (en supposant que est la taille d'un int) et les 4 octets suivants pour être le pointeur. En raison de l'endianness et de l'arrangement particuliers des arguments sur votre système, les 4 premiers octets du long long int se trouvent être les octets les moins significatifs de votre nombre, donc il imprime 123. Puis pour le pointeur d'objet, il lit les 4 octets suivants, dans ce cas, les octets les plus significatifs de votre numéro, qui est tous 0, sont interprétés comme un pointeur nil. Le pointeur réel n'est jamais lu.

Questions connexes