Можно ли увидеть код, сгенерированный ARC во время компиляции?

Я прочитал Переход к заметкам о выпуске ARC в разделе "Резюме". Они сказали:

ARC работает, добавляя код во время компиляции, чтобы обеспечить жизнь объектов до тех пор, пока это необходимо, но уже нет. По идее, это следует тем же соглашения об управлении памятью в качестве ручного подсчета ссылок (описано в Руководстве по программированию управления расширенной памятью), добавив для вас требуется соответствующее управление памятью.

Чтобы компилятор мог генерировать правильный код

Интересно, какой результат ARC исправил наш код.

Мой вопрос: Можем ли мы увидеть изменения? (В терминах выделения, сохранения, назначения или отпускания. Нет уровня сборки!)

Причина: Потому что я думаю, что хорошо видеть код лучшей практики в старой традиции без ARC-режима.

Ответы

Ответ 1

ARC в clang не работает, переписывая код из ObjC в ObjC, но выдавая дополнительный бит LLVM сохранения/выпуска во время кода-gen. Это означает, что невозможно понять, как компилятор "исправит" его, не перейдя на уровень LLVM IR/assembly.


Если ARC испускает бит-код LLVM, как вы сказали. Это сделано для этой цели, которая использует меньше времени в процессе компиляции? (менее сложный код ObjC, меньше заголовочный файл?)

Всегда лучше, если компилятор может уменьшить количество проходов через код.


Можете ли вы показать мне пример или утилиту, отображающую код на уровне сборки?

Чтобы получить код сборки, вы можете либо

  • Создайте сборку непосредственно из компилятора. В командной строке добавьте флаг -S при вызове компилятора. Результатом является файл .S, содержащий код сборки. В проекте Xcode откройте файл исходного кода, затем перейдите в Продукт (в строке меню) → Создать вывод Файл сборки.. p >

  • Сгенерируйте объектный файл, а затем разберите его. Встроенная команда otool -tvV <file> может выполнять разборку, и есть дополнительные инструменты, такие как otx (бесплатно) или IDA (бесплатно для оценки).

Я предпочитаю маршрут 2, потому что он генерирует меньше мусора, и инструмент разборки можно настроить для получения более полезной информации. В любом случае, с помощью любого метода вам нужно будет прочитать код сборки.

Возьмите этот код в качестве примера:

- (BOOL)application:(UIApplication*)application 
        didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
   self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   self.window.backgroundColor = [UIColor whiteColor];
   [self.window makeKeyAndVisible];
   return YES;
}

После компиляции будет произведена следующая сборка (проанализирована с использованием IDA):

-[SomeAppDelegate application:didFinishLaunchingWithOptions:]:
    push       {r4-r7,lr}
    add        r7, sp, #0xC
    str.w      r8, [sp,-#0x4]!
    sub        sp, sp, #0x18
    movw       r1, #(0x343c - 0x2574)       ; @selector(alloc)
    mov        r8, r0
    movt.w     r1, #0
    mov        r0, (0x3464 - 0x2576)        ; _OBJC_CLASS_$_UIWindow
    add        r1, pc
    add        r0, pc
    ldr        r1, [r1]
    ldr        r0, [r0]
    blx        _objc_msgSend
    mov        r1, (0x3440 - 0x258e)        ; @selector(mainScreen)
    mov        r6, r0
    movw       r0, #(0x3468 - 0x2594)       ; _OBJC_CLASS_$_UIScreen
    add        r1, pc
    movt.w     r0, #0
    add        r0, pc
    ldr        r1, [r1]
    ldr        r0, [r0]
    blx        _objc_msgSend
    mov        r7, r7
    blx        _objc_retainAutoreleasedReturnValue
    mov        r5, r0
    cbz        r5, L25ba
    movw       r0, #(0x3444 - 0x25b2)       ; @selector(bounds)
    mov        r1, r5
    movt.w     r0, #0
    add        r0, pc
    ldr        r2, [r0]
    add        r0, sp, #0x8
    blx        _objc_msgSend_stret
    b          L25c4

L25ba:
    add        r0, sp, #0x8
    vmov.i32   q8, #0x80
    vstmia     r0, {d16-d17}

L25c4:
    mov        r1, (0x3448 - 0x25d2)        ; @selector(initWithFrame:)
    ldr        r0, [sp,#0x10]
    add        r1, pc
    ldr        r2, [sp,#0x8]
    ldr        r3, [sp,#0xc]
    ldr        r4, [sp,#0x14]
    stmea.w    sp, {r0,r4}
    mov        r0, r6
    ldr        r1, [r1]
    blx        _objc_msgSend
    mov        r4, r0
    mov        r0, (0x344c - 0x25F2)        ; @selector(setWindow:)
    mov        r2, r4
    add        r0, pc
    ldr        r1, [r0]
    mov        r0, r8
    blx        _objc_msgSend
    mov        r0, r4
    blx        _objc_release
    mov        r0, r5
    blx        _objc_release
    mov        r0, (0x3450 - 0x2610)        ; @selector(window)
    add        r0, pc
    ldr        r5, [r0]
    mov        r0, r8
    mov        r1, r5
    blx        _objc_msgSend
    mov        r7, r7
    blx        _objc_retainAutoreleasedReturnValue
    mov        r1, (0x3454 - 0x2630)        ; @selector(whiteColor)
    mov        r6, r0
    movw       r0, #(0x346C - 0x2636)       ; _OBJC_CLASS_$_UIColor
    add        r1, pc
    movt.w     r0, #0
    add        r0, pc
    ldr        r1, [r1]
    ldr        r0, [r0]
    blx        _objc_msgSend
    mov        r7, r7
    blx        _objc_retainAutoreleasedReturnValue
    mov        r4, r0
    mov        r0, (0x3458 - 0x2652)        ; @selector(setBackgroundColor:)
    mov        r2, r4
    add        r0, pc
    ldr        r1, [r0]
    mov        r0, r6
    blx        _objc_msgSend
    mov        r0, r4
    blx        _objc_release
    mov        r0, r6
    blx        _objc_release
    mov        r0, r8
    mov        r1, r5
    blx        _objc_msgSend
    mov        r7, r7
    blx        _objc_retainAutoreleasedReturnValue
    mov        r4, r0
    mov        r0, (0x345C - 0x2680)        ; @selector(makeKeyAndVisible)
    add        r0, pc
    ldr        r1, [r0]
    mov        r0, r4
    blx        _objc_msgSend
    mov        r0, r4
    blx        _objc_release
    movs       r0, #1
    add        sp, sp, #0x18
    ldr.w      r8, [sp], #4
    pop        {r4-r7,pc}

Не вдаваясь в подробности, вы можете увидеть, что существует много _objc_release и _objc_retainAutoreleasedReturnValue. Это то, что ARC вставляет во время генерации кода. Декомпилируя его вручную, мы получим:

UIScreen* r5 = objc_retainAutoreleasedReturnValue([UIScreen mainScreen]);
CGRect sp8 = r5 != nil ? [r5 bounds] : CGRectZero;
UIWindow* r4 = [[UIWindow alloc] initWithFrame:sp8];
[self setWindow:r4];
objc_release(r4);
objc_release(r5);

UIWindow* r6a = objc_retainAutoreleasedReturnValue([self window])
UIColor* r4a = objc_retainAutoreleasedReturnValue([UIColor whiteColor])
[r6a setBackgroundColor:r4a];
objc_release(r4a);
objc_release(r6a);

UIWindow* r4b = objc_retainAutoreleasedReturnValue([self window])
[r4b makeKeyAndVisible];
objc_release(r4b);

return 1;

который является тем же самым, что описывает @c roald.

Ответ 2

Майк Эш очень освещает обсуждение реализации ARC здесь: http://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html

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

Таким образом, короткий ответ заключается в том, что ARC не использует те же методы [сохранения]/[выпуска], которые мы использовали бы в более старых версиях Objecive C, поэтому, поскольку ARC-preprocessed code не обязательно будет инструктировать вы в том, как сделать это сами.

ARC не является необычным в реализации как шаг предварительной обработки в компиляторе - я считаю, что многие функции Objective C реализованы таким образом.

Ответ 3

Нет, вы не можете вдаваться в такие подробности, не вдаваясь в детали уровня LLVM на уровне сборки.