apple 為什麼要改 objc_msgSend 的類型申明

来源:https://www.cnblogs.com/pencilCool/archive/2022/06/25/16410845.html
-Advertisement-
Play Games

Android multiple back stacks導航 談談android中多棧導航的幾種實現. 什麼是multiple stacks 當用戶在app里切換頁面時, 會需要向後回退到上一個頁面, 頁面歷史被保存在一個棧里. 在Android里我們經常說"back stack". 有時候在app ...


原文: https://mikeash.com/pyblog/objc_msgsends-new-prototype.html

總結 :

objc_msgSend 類型申明改變的原因: 讓錯誤在編譯的時候發生,而不是等到運行時。
為什麼有 運行時錯誤 : ABI 的錯配,調用方的ABI (對參數的傳遞) 和 接受方ABI( 對參加的處理)錯配了
為什麼 錯配: 我傳的是float ,你把我當double 了,浮點 轉成 雙精度浮點,這可不是和short 轉 int 單單高位補幾個0就可以了
為什麼float 專 double 了: C 語言 經常搞這種騷操作,畢竟處理數據的時候,用更高的精度有好處。
怎麼阻止 float 轉 double : 可以把函數中的參數類型申明成float ,而不是模棱兩可
等等函數參數 怎麼會模棱兩可: C 語言 變參函數瞭解一下 ,也就是void functionA(int a,int b, ...) 這種點點點函數,不幸原來的objc_msgSend 就是長這個樣子的

翻譯筆記:

objc_msgSend 變了

objc_msgSend 和 objc_msgSendSuper 的類型申明改了,那他們實際上接受什麼參數,以及它實際上返回什麼?

objc_msgSend 不得不用彙編

objc_msgSend 是用彙編實現的,不只是為了快,只用 C 是實現不了objc_msgSend的功能是的(pencilCool: 因為不能去擾亂傳參的寄存器呀 )

objc_msgSend 做的基礎工作

objc_msgSend的fast path 做了幾件關鍵的事情:

  • 載入對象的類
  • 在該類的方法緩存中查找selector 。
  • 跳轉到在緩存中找到的方法實現。

objc_msgSend 不幹擾函數傳參的寄存器

因為objc_msgSend直接跳到了方法實現,而沒有進行函數調用,所以一旦它的工作完成,它就有效地消失了。
這個實現很謹慎,沒有干擾任何可以用來向函數傳遞參數的寄存器。
調用者在調用objc_msgSend時,就像直接調用方法實現一樣,以同樣的方式傳遞所有的參數。
一旦objc_msgSend查找到實現並跳轉到它,這些參數仍然是實現所期望的位置。當實現返回時,它直接返回給調用者,而返回值是由標準機制提供的。

這就回答了上面的問題:objc_msgSend的原型是它最終調用的方法實現的原型。

舊的原型在某些情況下可以工作(當ABI匹配時),而在其他情況下卻奇怪地失敗(當ABI不匹配時)。

新的原型永遠不會工作,除非你先把它投到適當的類型。只要你把它投到正確的類型上,它就總是能工作。因此,新的做事方式鼓勵正確做事,並使做錯事變得更難。

最小的原型

儘管objc_msgSend的原型取決於將被調用的方法實現,但有兩件事在所有的方法實現中是共同的:第一個參數總是id self,第二個參數總是SEL _cmd。objc_msgSend需要這兩個信息來執行它的方法調度工作,所以它們必須總是在同一個地方,以便它能夠找到它們。

我們可以為objc_msgSend寫一個近似的泛化原型來表示這一點。

objc_msgSend(id self, SEL _cmd, ???)

其中,??的意思是我們不知道,它取決於將被調用的特定方法實現。當然,C語言沒有辦法表示這樣的通配符。

對於返回值,我們可以嘗試挑選一些常見的東西。因為Objective-C是關於對象的,所以假設返回值是id是有意義的。

id objc_msgSend(id self, SEL _cmd, ???)

這不僅涵蓋了返回值是一個對象的情況,也涵蓋了返回值是無效的情況,以及其他一些返回值是不同類型但沒有被使用的情況。

那麼參數呢?C語言實際上有一種方法來表示任意類型的任意數量的參數,其形式是可變參數函數原型。在參數列表的末尾有一個省略號,意味著後面有一個任意類型的可變數量的值。

id objc_msgSend(id self, SEL _cmd, ...)

這正是最近改變之前的原型。

ABI錯配

運行時的相關問題是調用地點的ABI是否與方法實現的ABI匹配。也就是說,接收者是否會從相同的位置,以調用者傳遞的相同格式檢索參數?如果調用者將一個參數放入 $rdx,那麼實現者就需要從 $rdx中獲取該參數,否則就會出現混亂。

最小的原型可能能夠表達傳遞任意數量的任意類型的概念,但是為了讓它在運行時真正發揮作用,它需要使用與方法實現相同的ABI。該實現幾乎肯定使用了不同的原型,並且通常有固定數量的參數。

不能保證一個變數函數的ABI與一個有固定參數數的函數的ABI相匹配。在一些平臺上,它們幾乎完全匹配。在其他平臺上,它們完全不匹配。

英特爾ABI

讓我們看一個具體的例子。macOS使用x86-64的標準System V ABI。ABI中有大量的細節,但我們將專註於基礎知識。

參數是在寄存器中傳遞的。整數參數在寄存器rdi、rsi、rdx、rcx、r8和r9中傳遞,按順序排列。浮點參數在SSE寄存器xmm0到xmm7中傳遞。當調用一個變數函數時,寄存器 al 被設置為用於傳遞參數的SSE寄存器的數量。整數返回值被放在rax和rdx中,浮點返回值被放在xmm0和xmm1中。

變參函數的ABI幾乎與普通函數的ABI相同。唯一的例外是傳遞al中使用的SSE寄存器的數量。然而,當使用變參函數 ABI調用普通函數時,這一點是無害的,因為普通函數會忽略al的內容。

C語言把事情搞得有點亂。C語言規定,當作為變數參數傳遞時,某些類型會被提升為更廣泛的類型。小於int的整數(如char和short)被提升為int,而float被提升為double。如果你的方法簽名中包括這些類型之一,那麼如果調用者使用變數原型,就不可能將參數傳遞為該確切的類型。

對於整數來說,這實際上並不重要。整數被存儲在相應的寄存器的底層位,無論哪種方式,其位數最終都在同一個地方。然而,這對浮點數來說是災難性的。將一個較小的整數轉換為int只需要用額外的位來填充它。將float轉換為double涉及到將數值完全轉換為一個不同的結構。float中的位與double中的相應位並不一致。如果你試圖使用一個變數原型來調用一個接受浮動參數的非變數函數,該函數將收到垃圾。

To illustrate this problem, here's a quick example:

    // Use the old variadic prototype for objc_msgSend.
    #define OBJC_OLD_DISPATCH_PROTOTYPES 1

    #import <Foundation/Foundation.h>
    #import <objc/message.h>

    @interface Foo : NSObject @end
    @implementation Foo
    - (void)log: (float)x {
        printf("%f\n", x);
    }
    @end

    int main(int argc, char **argv) {
        id obj = [Foo new];
        [obj log: (float)M_PI];
        objc_msgSend(obj, @selector(log:), (float)M_PI);
    }

It produces this output:

    3.141593
    3370280550400.000000

正如你所看到的,當寫成消息發送時,這個值是正確的,但是當通過顯式調用objc_msgSend時,這個值被完全扭曲了。

這個問題可以通過轉換objc_msgSend的簽名來解決。回顧一下,objc_msgSend的實際原型是最終將被調用的方法的原型,所以使用它的正確方法是將其轉換為相應的函數指針類型。這個調用可以正常工作。

((void (*)(id, SEL, float))objc_msgSend)(obj, @selector(log:), M_PI);

ARM64 ABI

讓我們看看另一個相關的例子。iOS使用了ARM64的標準ABI的一個變體。

整數參數在寄存器x0到x7中傳遞。浮點參數在v0到v7中傳遞。 其他參數在堆棧中傳遞。返回值被放置在作為參數傳遞的同一個或多個寄存器中。

這隻適用於普通參數。變數參數從不在寄存器中傳遞。它們總是在堆棧中傳遞,即使是在有參數寄存器的情況下。

沒有必要仔細分析這在實踐中是如何實現的。ABI是完全不匹配的,一個方法在被調用的時候,如果沒有cast objc_msgSend,那麼它的參數中就會收到垃圾。

新的原型

新的原型簡短而貼心。

void objc_msgSend(void)。

這一點都不正確。然而,舊的原型也不正確。這個原型的錯誤更明顯,這是件好事。舊的原型使你可以很容易地使用它而不去鑄造它,並且經常工作,以至於你很容易就會認為一切都很好。當你遇到有問題的情況時,這些錯誤就非常不清楚了。

這個原型甚至不允許你傳遞self和_cmd這兩個必要參數。你可以在完全沒有參數的情況下調用它,但它會立即崩潰,這應該是很明顯的出錯原因。如果你試圖使用它而不進行轉換,編譯器會抱怨,這比奇怪的破參數值要好得多。

因為它仍然有一個函數類型,你仍然可以把它投到一個適當類型的函數指針上,然後用這種方式調用它。只要你把類型弄對了,這就能正確工作。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 用例演示 - 創建實體 本節將演示一些示例用例並討論可選場景。 創建實體 從實體/聚合根類創建對象是實體生命周期的第一步。聚合/聚合根規則和最佳實踐部分 建議為Entity類創建一個主構造函數,以保證創建一個有效的實體。因此,無論何時我們需要創建實體的實例,我們都應該使用那個構造函數 參見下麵的問題 ...
  • 痞子衡嵌入式半月刊: 第 57 期 這裡分享嵌入式領域有用有趣的項目/工具以及一些熱點新聞,農曆年分二十四節氣,希望在每個交節之日準時發佈一期。 本期刊是開源項目(GitHub: JayHeng/pzh-mcu-bi-weekly),歡迎提交 issue,投稿或推薦你知道的嵌入式那些事兒。 上期回顧 ...
  • 2022年6月初合宙新上市了 Air32F103 系列 MCU, 分 Air32F103CBT6 和 Air32F103CCT6 兩個型號, 分別是 32K RAM + 128K FLASH 和 32K RAM + 256K FLASH, 支持的最高主頻216MHz, 可以Pin2Pin替換STM3... ...
  • ## 電腦性能設置 優化設置一:開啟卓越性能 其實,win10系統中有一個卓越性能的隱藏設置,它可以讓我們的電腦,在現有配置的情況下,發揮出最優良的性能。 1、 同時按住“win+R”打開運行視窗,輸入“powershell”並點擊“確定” 2、 打開命令提示符頁面後,輸入並執行以下字元,就會出現 ...
  • 寫在前面: 這幾天留校,在做一個電機驅動的項目,使用的是合肥傑發的平臺,車規級晶元AC7801/11系列晶元。 但在進行模擬和程式燒錄的時候遇到了各種問題,藉助這個機會,私下裡總結了常見的模擬與燒錄程式常見的幾種方式,以及相關的操作,希望對大家有幫助。 單片機ISP、IAP和ICP幾種燒錄方式的區別 ...
  • 對於 MySQL 資料庫作為各個業務系統的存儲介質,在系統中承擔著非常重要的職責,如果資料庫崩了,那麼對於讀和寫資料庫的操作都會受到影響。如果不能迅速恢復,對業務的影響是非常大的。本文我將分享MySQL 雙主 + Keepalived 的高可用落地和踩坑之路。 ...
  • 1、簡述 binlog 二進位日誌文件,這個文件記錄了MySQL所有的DML操作。通過binlog日誌我們可以做數據恢復,增量備份,主主複製和主從複製等等。 2、Docker中無法使用vim問題解決 https://blog.csdn.net/Tomwildboar/article/details/ ...
  • 當用戶有跨語種交流或音頻內容翻譯的需求時,應用需要能自動檢測語音內容再輸出為用戶需要的語言文字。 HMS Core機器學習服務提供同聲傳譯能力,同聲傳譯實現將實時輸入的長語音實時翻譯為不同語種的文本以及語音,並實時輸出原語音文本、翻譯後的文本以及翻譯文本的語音播報。 在直播類,會議類的應用中,同聲傳 ...
一周排行
    -Advertisement-
    Play Games
  • Github / Gitee QQ群(1群) : 813100564 / QQ群(2群) : 579033769 視頻教學 介紹 MiniWord .NET Word模板引擎,藉由Word模板和數據簡單、快速生成文件。 Getting Started 安裝 nuget link : https:// ...
  • Array.Sort Array類中相當實用的我認為是Sort方法,相比起冗長的冒泡排序,它的出現讓排序更加的簡化 結果如下: 還可以聲明一個靜態方法用來專門調用指定數組排序,從名為 array 的一維數組中 a 索引處開始,到 b 元素 從小到大排序。 註意: a + b 不能大於 array 的 ...
  • 前言 在上一篇文章CLR類型系統概述里提到,當運行時掛起時, 垃圾回收會執行堆棧遍歷器(stack walker)去拿到堆棧上值類型的大小和堆棧根。這裡我們來翻譯BotR里一篇專門介紹Stackwalking的文章,希望能加深理解。 順便說一句,StackWalker在中文里似乎還沒有統一的翻譯,J ...
  • 使用過 nginx 的小伙伴應該都知道,這個中間件是可以設置跨域的,作為今天的主角,同樣的 反向代理中間件的 YARP 毫無意外也支持了跨域請求設置。 有些小伙伴可能會問了,怎樣才算是跨域呢? 在 HTML 中,一些標簽,例如 img、a 等,還有我們非常熟悉的 Ajax,都是可以指向非本站的資源的 ...
  • 什麼是Git Git 是一個開源的分散式版本控制系統,用於敏捷高效地處理任何或小或大的項目。 Git 是 Linus Torvalds 為了幫助管理 Linux 內核開發而開發的一個開放源碼的版本控制軟體。 Git 與常用的版本控制工具 CVS, Subversion 等不同,它採用了分散式版本庫的 ...
  • 首先CR3是什麼,CR3是一個寄存器,該寄存器內保存有頁目錄表物理地址(PDBR地址),其實CR3內部存放的就是頁目錄表的記憶體基地址,運用CR3切換可實現對特定進程記憶體地址的強制讀寫操作,此類讀寫屬於有痕讀寫,多數驅動保護都會將這個地址改為無效,此時CR3讀寫就失效了,當然如果能找到CR3的正確地址... ...
  • 說明 onlyoffice為一款開源的office線上編輯組件,提供word/excel/ppt編輯保存操作 以下操作均基於centos8系統,officeonly鏡像版本7.1.2.23 鏡像下載地址:https://yunpan.360.cn/surl_y87CKKcPdY4 (提取碼:1f92 ...
  • 二叉樹查找指定的節點 前序查找的思路 1.先判斷當前節點的no是否等於要查找的 2.如果是相等,則返回當前節點 3.如果不等,則判斷當前節點的左子節點是否為空,如果不為空,則遞歸前序查找 4.如果左遞歸前序查找,找到節點,則返回,否繼續判斷,當前的節點的右子節點是否為空,如果不為空,則繼續向右遞歸前 ...
  • ##Invalid bound statement (not found)出現原因和解決方法 ###前言: 想必各位小伙伴在碼路上經常會碰到奇奇怪怪的事情,比如出現Invalid bound statement (not found),那今天我就來分析以下出現此問題的原因。 其實出現這個問題實質就是 ...
  • ###一、背景知識 爬蟲的本質就是一個socket客戶端與服務端的通信過程,如果我們有多個url待爬取,只用一個線程且採用串列的方式執行,那隻能等待爬取一個結束後才能繼續下一個,效率會非常低。 需要強調的是:對於單線程下串列N個任務,並不完全等同於低效,如果這N個任務都是純計算的任務,那麼該線程對c ...