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
  • 一:背景 準備開個系列來聊一下 PerfView 這款工具,熟悉我的朋友都知道我喜歡用 WinDbg,這東西雖然很牛,但也不是萬能的,也有一些場景他解決不了或者很難解決,這時候藉助一些其他的工具來輔助,是一個很不錯的主意。 很多朋友喜歡在項目中以記錄日誌的方式來監控項目的流轉情況,其實 CoreCL ...
  • 本來閑來無事,準備看看Dapper擴展的源碼學習學習其中的編程思想,同時整理一下自己代碼的單元測試,為以後的進一步改進打下基礎。 突然就發現問題了,源碼也不看了,開始改代碼,改了好久。 測試Dapper.LiteSql數據批量插入的時候,耗時20秒,感覺不正常,於是我測試了非Dapper版的Lite ...
  • 需求如下,在DEV框架項目中,需要在表格中增加一列顯示圖片,並且能編輯該列圖片,然後進行保存等操作,最終效果如下 這裡使用的是PictureEdit控制項來實現,打開DEV GridControl設計器,在ColumnEdit選擇PictureEdit: 綁定圖片代碼如下: DataTable dtO ...
  • 前兩天微軟偷偷更新了Visual Studio 2022 正式版版本 17.3 發佈,發佈摘要: MAUI 工作負荷 GA 生成 MAUI/Blazor CSS 熱重載支持 現在,你將能夠使用我們的新增功能在 Visual Studio 中使用每個更新試用一系列新功能。 選擇每個功能以瞭解有關特定功 ...
  • 航天和軍工領域的數字化轉型和建設正在積極推進,在與航天二院、航天三院、航天六院、航天九院、無線電廠、兵工廠等單位交流的過程中,用戶更聚焦試驗和生產過程中的痛點,迫切需要解決軟體平臺統一監測和控制設備及軟體與設備協同的問題。 ...
  • .NET 項目預設情況下 日誌是使用的 ILogger 介面,預設提供一下四種日誌記錄程式: 控制台 調試 EventSource EventLog 這四種記錄程式都是預設包含在 .NET 運行時庫中。關於這四種記錄程式的詳細介紹可以直接查看微軟的官方文檔 https://docs.microsof ...
  • 一:背景 上一篇我們聊到瞭如何去找 熱點函數,這一篇我們來看下當你的程式出現了 非托管記憶體泄漏 時如何去尋找可疑的代碼源頭,其實思路很簡單,就是在 HeapAlloc 或者 VirtualAlloc 時做 Hook 攔截,記錄它的調用棧以及分配的記憶體量, PerfView 會將這個 分配量 做成一個 ...
  • 背景 在 CI/CD 流程當中,測試是 CI 中很重要的部分。跟開發人員關係最大的就是單元測試,單元測試編寫完成之後,我們可以使用 IDE 或者 dot cover 等工具獲得單元測試對於業務代碼的覆蓋率。不過我們需要一個獨立的 CLI 工具,這樣我們才能夠在 Jenkins 的 CI 流程集成。 ...
  • 一、應用場景 大家在使用Mybatis進行開發的時候,經常會遇到一種情況:按照月份month將數據放在不同的表裡面,查詢數據的時候需要跟不同的月份month去查詢不同的表。 但是我們都知道,Mybatis是ORM持久層框架,即:實體關係映射,實體Object與資料庫表之間是存在一一對應的映射關係。比 ...
  • 我國目前並未出台專門針對網路爬蟲技術的法律規範,但在司法實踐中,相關判決已屢見不鮮,K 哥特設了“K哥爬蟲普法”專欄,本欄目通過對真實案例的分析,旨在提高廣大爬蟲工程師的法律意識,知曉如何合法合規利用爬蟲技術,警鐘長鳴,做一個守法、護法、有原則的技術人員。 案情介紹 深圳市快鴿互聯網科技有限公司 2 ...