I recently wasted about half an hour tracking down this odd behavior in NSLog(...):
NSString *text = @"abc";
long long num = 123;
NSLog(@"num=%lld, text=%@",num,text); //(A)
NSLog(@"num=%d, text=%@",num,text); //(B)
Line (A) prints the expected "num=123, text=abc", but line (B) prints "num=123, text=(null)".
Obviously, printing a long long
with %d
is a mistake, but can someone explain why it would cause text
to be printed as null?
You just messed up memory alignment on your stack. I assume than you use newest Apple product with x86 processor. Taking these assumptions into account your stack looks like that in both situations:
| stack | first | second |
+---------------------+-------+--------+
| 123 | | %d |
+---------------------+ %lld +--------+
| 0 | | %@ |
+---------------------+-------+--------+
| pointer to text | %@ |ignored |
+---------------------+-------+--------+
In first situation you put on stack 8 bytes and then 4 bytes. And than NSLog is instructed to take back from stack 12 bytes (8 bytes for %lld
and 4 bytes for %@
).
In second situation you instruct NSLog to first take 4 bytes (%d
). Since your variable is 8 bytes long and holds really small number its upper 4 bytes will be 0. Then when NSLog will try to print text it will take nil
from stack.
Since sending message to nil
is valid in Obj-C NSLog will just send description:
to nil
get probably nothing and then print (null).
In the end since Objective-C is just C with additions, caller cleans up whole this mess.
How varargs are implemented is system-dependent. But what is likely happening is that the arguments are stored consecutivelyly in a buffer, even though the arguments may be different sizes. So the first 8 bytes (assuming that's the size of a long long int
) of the arguments is the long long int
, and the next 4 bytes (assuming that's the size of a pointer on your system) is the NSString
pointer.
Then when you tell the function that it expects an int
and then a pointer, it expect the first 4 bytes to be the int
(assuming that's the size of an int
) and the next 4 bytes to be the pointer. Because of the particular endianness and arrangement of arguments on your system, the first 4 bytes of the long long int
happens to be the least significant bytes of your number, so it prints 123. Then for the object pointer, it reads the next 4 bytes, which in this case is the most significant bytes of your number, which is all 0, so that gets interpreted as a nil
pointer. The actual pointer never gets read.