Страницы

Поиск по вопросам

пятница, 10 января 2020 г.

сравнения в Ruby

#ruby


x = 10      
xx = 10 




1. x == xx                      #true
2. x.eql?xx                     #true
3. x.equal?xx                   #true
4. x.object_id == xx.object_id  #true


Бородатые дядьки объясните почему в строке 3 и 4 возвращается true.
    


Ответы

Ответ 1



Осторожно: ответ написан, когда актуальным был MRI 2.3. В 2.4 Fixnum и Bignum сведены в один тип Integer. Но описываемая особенность всё ещё имеет место, просто если раньше числа ей подверженные было легко распознать по классу, теперь это глубоко закопанная деталь реализации. Потому что (следуя семантике Object#eql? и Object#equal?) в x и xx не просто равное, а одно и то же значение, равны даже ссылки (в Ruby, напоминаю, все значения это ссылки, с парой оговорок, об одной из которых я сейчас и расскажу). В MRI если посмотреть в object_id (который фактически выводит значение ссылки; самой ссылки, а не значения за ней), можно увидеть, что у равных Fixnum'ов (литерал 10 представляет один из них) одинаковые object_id при любых обстоятельствах: литерал ли это в коде, результат ли вычислений... Кэш? То есть, MRI кэширует объекты чисел и достаёт их по значению? Почти. Но не совсем. Безумные бинарные хаки "Ссылка" на Fixnum в MRI содержит его значение внутри, прямо в ссылке. Если у большинства объектов object_id это указатель на объект в памяти (или адрес объекта в памяти), то у Fixnum (и у некоторых других классов) эта "ссылка", если интерпретировать её именно как указатель ведёт... куда-то, куда лучше не смотреть. Там может быть что-то интересное, а может быть чужая память. Потому что у Fixnum'ов это не указатель. object_id у Fixnum это значение числа, сдвинутое на 1 влево, и 1 на младшем бите. Если на время забыть об отрицательных числах, то чтобы получить object_id любого Fixnumа, надо его умножить на 2 и прибавить 1. (0..100_000).all? { |i| i.object_id == (i * 2 + 1) } # => true С отрицательными числами всё несколько сложнее, поскольку числа хранятся в дополнительном коде. Нужно пользоваться уже битовой арифметикой: (-100_000..100_000).all? { |i| i.object_id == (i << 1 | 1) } # => true Каким образом MRI догадывается, является ли значение указателем на объект в памяти или Fixnumом? Очень просто, по младшему биту (на самом деле не только). В объектом пространстве Ruby не используются нечётные указатели, поскольку все типы имеют размер больше 2 байт. Чуть более наглядно (и на английском) можно почитать об этом в этой статье, а можно даже покопаться в исходном коде Ruby (вот преобразование из Fixnum в сишный long). Но я здесь говорю исключительно об MRI. Гарантируется ли такое поведение во всех реализациях? Поскольку спецификации у Ruby, как у языка (а не реализации), нет, то нет.

Комментариев нет:

Отправить комментарий