Многократный вызов variadic-метода Obj-C приводит к появлению недопустимых аргументов

Весь этот ответ находится в контексте соответствия спецификации, а не того, что движок делает в определенный момент или исторически.

Как правило, нет

. Фактический вопрос очень расплывчатый.

будут ли свойства в том же порядке, что я добавил их

В каком контексте?

Ответ: это зависит от ряда факторов. В общем, no .

Иногда да

Здесь вы можете рассчитывать на порядок ключей свойств для plain Objects:

  • Соответствующий ES2015 двигатель
  • Собственные свойства
  • Object.getOwnPropertyNames(), Reflect.ownKeys(), Object.getOwnPropertySymbols(O)

Во всех случаях эти методы включают неперечислимые ключи свойств и ключи заказа, указанные в [[OwnPropertyKeys]] (см. ниже). Они различаются по типу ключевых значений, которые они включают (String и / или Symbol). В этом контексте String включает целые значения.

Object.getOwnPropertyNames(O)

Возвращает собственные свойства String O ] ).

Reflect.ownKeys(O)

Возвращает O собственные String - и Symbol клавиши свойства.

Object.getOwnPropertySymbols(O)

Возвращает собственные свойства Symbol O.

[[OwnPropertyKeys]]

Порядок по существу: целочисленный Strings в порядке возрастания, нецелоподобный Strings в порядке создания, Символы в порядке создания. В зависимости от того, какая функция вызывает это, некоторые из этих типов могут не включаться.

Специфическим языком является то, что ключи возвращаются в следующем порядке:

  1. .. каждый собственный ключ свойства P O [объект, который повторяется], который является целым индексом, в порядке возрастания числового индекса
  2. ... каждый собственный ключ свойства P из O который является строкой, но не является целым индексом, в порядке создания свойства
  3. ... каждый собственный ключ свойства P из O, который является символом, в порядке создания свойства

Map

Если вас интересуют упорядоченные карты, вы должны рассмотреть возможность использования типа Map, введенного в ES2015, вместо простого Objects.

2
задан Max 1 March 2019 в 20:29
поделиться

1 ответ

Ваше нулевое завершение в конце списка аргументов передается вашей функции как int (32-битное) значение, но ваш va_arg вытягивает значение NSInteger (он же long) из стека. Таким образом, вы извлекаете дополнительные 32 бита из стека и смотрите на все это как на одно 64-битное значение, половина которого - это ноль, который вы намеревались, а половина - то, что произошло рядом с ним в памяти. в то время.

Вы должны сделать это, чтобы получить желаемое поведение:

[var test:Enum1, Enum2, Enum3, Enum4, Enum5, Enum6, (NSInteger)0];

Как поясняет Роб Мейофф в комментариях ниже, литерал с нулевым нулем, являющийся целочисленным значением, рассматривается как [115 ]. Применяются обычные целочисленные правила продвижения C *; в списке varargs, поскольку аргументы не имеют объявленного типа, более целочисленные типы повышаются до - и передаются как - int с. Поскольку компилятор не имеет представления о фактических типах, которые вы ожидаете увидеть во время выполнения, этот int не будет повышен до long для вас, поэтому он попадает в стек как int.

Как правило, завершение varargs выполняется либо неявно (предвидение стиля printf числа и типов arg), либо с константой, подобной nil, которая будет правильной ширины, и эти подходы позволяют избежать этой проблемы. В старом 32-битном мире, где значения enum были int с, и NSInteger были int, и целочисленное продвижение по умолчанию было , также - int, эти различия были скрыты.

На практике это может означать для вашего дизайна кода то, что вы, возможно, зарезервировали бы специальное значение перечисления (не обязательно ноль), чтобы использовать его в качестве ограничителя списка, чтобы гарантировать, что он имеет правильный тип. Или вы, возможно, измените, добавив число аргументов к вызову функции.

* См. Стандарт C18 §6.3.1.1 ;-)


Объяснитель бонуса: почему вы увидели значение enum == 4294967296?

[1133 ] Десятичное число 4294967296 равно 0x0000000100000000. Вторая (нижняя) половина этого значения была 32-битным нулем, который вы поставили туда. Верхняя половина выглядит так, как будто это просто число 1. Сначала я предполагал, что это будет какая-то другая (действительная) часть текущего стекового фрейма, но некоторые исследования (на 64-битном Mac, использующем текущий llvm с Xcode и т. Д.) Показывают, что компилятор устанавливает вызов -test:... путем нажатия на enum args с помощью movq (move quad = 64bit) и конечного нулевого arg с movl (move long = 32bit). Поскольку стек выровнен на 64 бита, в стеке памяти между предпоследним и конечным аргументами остается «дыра» в 32 бита (т.е. не перезаписывается). Это место, которое содержало 0x1. Таким образом, вы не читаете соседний аргумент или другое значение, которое является допустимым, но используется для чего-то другого. Вы читаете призрачное значение из рабочего пространства какого-то более раннего вызова функции. В моей отладке это не похоже на alloc / init из Variable - это что-то предшествующее и не связанное с тестовым кодом под рукой.

0
ответ дан Ben Zotto 1 March 2019 в 20:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: