NSLog (& # 8230;) неправильный спецификатор формата влияет на другие переменные?
-
22-07-2019 - |
Вопрос
Недавно я потратил около получаса, чтобы отследить это странное поведение в NSLog (...):
NSString *text = @"abc";
long long num = 123;
NSLog(@"num=%lld, text=%@",num,text); //(A)
NSLog(@"num=%d, text=%@",num,text); //(B)
Строка (A) печатает ожидаемое "num = 123, text = abc", но строка (B) печатает "num = 123, text = (null) ". р>
Очевидно, что печать long long
с % d
является ошибкой, но кто-то может объяснить, почему это приведет к тому, что text
будет напечатано как нуль?
Решение
Вы только что испортили выравнивание памяти в вашем стеке. Я предполагаю, что вы используете новейший продукт Apple с процессором x86. Принимая во внимание эти предположения, ваш стек выглядит так в обеих ситуациях:
| stack | first | second | +---------------------+-------+--------+ | 123 | | %d | +---------------------+ %lld +--------+ | 0 | | %@ | +---------------------+-------+--------+ | pointer to text | %@ |ignored | +---------------------+-------+--------+
В первой ситуации вы кладете в стек 8 байтов, а затем 4 байта. И затем NSLog получает команду извлечь из стека 12 байтов (8 байтов для % lld
и 4 байта для % @
). Р>
Во второй ситуации вы указываете NSLog сначала занять 4 байта (% d
). Поскольку ваша переменная имеет длину 8 байт и содержит действительно небольшое число, ее верхние 4 байта будут равны 0. Тогда, когда NSLog попытается напечатать текст, он возьмет nil
из стека.
Поскольку отправка сообщения nil
в Obj-C допустима, NSLog просто отправит description:
в nil
, вероятно, ничего не получит, а затем напечатает ( нуль).
В конце концов, поскольку Objective-C - это просто C с добавлениями, вызывающая сторона устраняет весь этот беспорядок. Р>
Другие советы
Реализация varargs зависит от системы. Но, вероятно, происходит то, что аргументы хранятся последовательно в буфере, даже если аргументы могут быть разных размеров. Таким образом, первые 8 байтов (при условии, что это размер long long int
) аргументов - это long long int
, а следующие 4 байта (при условии, что это размер указатель в вашей системе) является указателем NSString
.
Затем, когда вы сообщаете функции, что она ожидает int
, а затем указатель, она ожидает, что первые 4 байта будут int
(при условии, что это размер int
) и следующие 4 байта должны быть указателями. Из-за особого порядка байтов и расположения аргументов в вашей системе первые 4 байта long long int
оказываются наименее значимыми байтами вашего числа, поэтому он печатает 123. Затем для указателя объекта он считывает следующие 4 байта, которые в данном случае являются самыми значимыми байтами вашего числа, равными 0, так что интерпретируется как указатель nil
. Фактический указатель никогда не читается.