printf內幕----編程內幕(1)

? ? 曾幾何時,您有沒有在夜深人靜的時候想過一個問題,printf內部究竟做了什么?為何可以輸出到屏幕上顯示出來?

? ? 先看看這段熟悉的代碼:

? ?

//
//  Created by xi.chen on 2017/9/2.
//  Copyright ? 2017 All rights reserved.
//#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>int main()
{printf("hello, my cat!\n");return 0;
}

環境:

Mac OSX 10.12.3

Apple LLVM version 8.1.0 (clang-802.0.42)

Target: x86_64-apple-darwin16.4.0

Xcode 8.3.3

首先,我們先看看匯編代碼.

(__TEXT,__text) section
_main:
0000000100000f50	pushq	%rbp
0000000100000f51	movq	%rsp, %rbp
0000000100000f54	subq	$0x10, %rsp
0000000100000f58	leaq	0x3b(%rip), %rdi ## literal pool for: "hello, my cat!\n"
0000000100000f5f	movl	$0x0, -0x4(%rbp)
0000000100000f66	movb	$0x0, %al
0000000100000f68	callq	0x100000f7a ## symbol stub for: _printf
0000000100000f6d	xorl	%ecx, %ecx
0000000100000f6f	movl	%eax, -0x8(%rbp)
0000000100000f72	movl	%ecx, %eax
0000000100000f74	addq	$0x10, %rsp
0000000100000f78	popq	%rbp
0000000100000f79	retq

可以看到,核心就是

callq 0x100000f7a ## symbol stub for: _printf

0000000100000f68
f68是在可執行文件的偏移.
可以用MachOView查看可執行文件的內部結構.

0000f60 45 fc 00 00 00 00 b0 00 e8 0d 00 00 00 31 c9 89

指令e8 0d 00 00 00 是call指令,相當于調用子程序,跳轉到當前指令后一條指令的PC值(0x100000f6d) + 偏移值(0d), 即0x100000f7a.
也就是上面callq之后的地址值.


上面沒有分析,0x100000000是什么?

xichen:hello xichen$ xcrun size -x -l -m !$
xcrun size -x -l -m /Users/xichen/Library/Developer/Xcode/DerivedData/hello-bkhrmjvnrfikgkfgkiemoyorgkig/Build/Products/Debug/hello
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)Section __text: 0x2a (addr 0x100000f50 offset 3920)Section __stubs: 0x6 (addr 0x100000f7a offset 3962)Section __stub_helper: 0x1a (addr 0x100000f80 offset 3968)Section __cstring: 0x10 (addr 0x100000f9a offset 3994)Section __unwind_info: 0x48 (addr 0x100000fac offset 4012)total 0xa2
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)total 0x18
Segment __LINKEDIT: 0x3000 (vmaddr 0x100002000 fileoff 8192)
total 0x100005000

這個數值可以看成加載器把可執行文件加載到內存的虛擬地址基址. (上面的所有指令都是基于這個地址為基址)
回到callq這條指令,會跳轉到地址0x100000f7a,對應的指令是:

0000f70 45 f8 89 c8 48 83 c4 10 5d c3 ff 25 90 00 00 00

ff指令是jmp指令, 反匯編如下:

(lldb) x/3i 0x100000f7a0x100000f7a: ff 25 90 00 00 00     jmpq   *0x90(%rip)               ; (void *)0x0000000100000f900x100000f80: 4c 8d 1d 81 00 00 00  leaq   0x81(%rip), %r11          ; (void *)0x0000000000000000

jmpq跳轉到: 0xf80 + 0x90地址的數值為地址的地方.

>>> hex(0xf80+0x90)
'0x1010'

即跳轉到0x100001010.

(lldb) x/3g 0x100001010
0x100001010: 0x00007fffa8778180 0x0000000000000000
0x100001020: 0x0000000000000000

我們來看看printf的地址在哪里:

(lldb) dis -s printf
libsystem_c.dylib`printf:0x7fffa8778180 <+0>:  pushq  %rbp0x7fffa8778181 <+1>:  movq   %rsp, %rbp0x7fffa8778184 <+4>:  pushq  %r150x7fffa8778186 <+6>:  pushq  %r140x7fffa8778188 <+8>:  pushq  %rbx0x7fffa8778189 <+9>:  subq   $0xd8, %rsp0x7fffa8778190 <+16>: movq   %rdi, %r140x7fffa8778193 <+19>: testb  %al, %al0x7fffa8778195 <+21>: je     0x7fffa87781c3            ; <+67>0x7fffa8778197 <+23>: movaps %xmm0, -0xc0(%rbp)


0x7fffa8778180是不是和上面對上來了?
我們繼續dump printf后面調用了什么:

(lldb)  x/50i 0x7fffa8778180
...............0x7fffa8778235: 48 0f 45 f0           cmovneq %rax, %rsi0x7fffa8778239: 48 8d 4d c0           leaq   -0x40(%rbp), %rcx0x7fffa877823d: 48 89 df              movq   %rbx, %rdi0x7fffa8778240: 4c 89 f2              movq   %r14, %rdx0x7fffa8778243: e8 c0 20 00 00        callq  0x7fffa877a308            ; vfprintf_l0x7fffa8778248: 4c 3b 7d e0           cmpq   -0x20(%rbp), %r150x7fffa877824c: 75 0e                 jne 

我們有幸可以看到mac開放的libc源代碼:

int
printf(char const * __restrict fmt, ...)
{int ret;va_list ap;va_start(ap, fmt);ret = vfprintf_l(stdout, __current_locale(), fmt, ap);va_end(ap);return (ret);
}

調用vfprintf_l, 是不是感覺一切都在預期之內呢?
繼續在libc跟蹤一番,會發現最終會調用write系統調用完成.
write系統調用會使用int指令陷入內核,執行寫數據的操作.

到此,您會不會有疑問,為何調用printf函數中跳轉了好多次,是因為編譯系統傻嗎?當然不是,因為采用的是動態鏈接庫, 主程序一開始并不知道調用的printf函數最終會在哪個地址,所以先保留了一個stub,等加載器加載運行時,再填入對應的地址.
就是上面的jmp跳轉所實現的, 而call printf這個語句并不需要等運行時再計算地址,編譯期就可以用此時設定的固定地址.

至此,我們已經理清了上面的flow. 那又是如何顯示在屏幕上的呢?
如果從終端terminal開始,調用了上面的應用程序(比如hello), ?terminal會fork一個進程, 并執行hello,然后等待hello完成 (此種是不帶后臺運行的模式).
hello調用了printf輸出,printf是向stdout輸出,為何向stdout會在此terminal上顯示呢?
首先我們要明白,stdout究竟指向哪個設備?

xichen:hello xichen$ tty
/dev/ttys001

所以,printf其實是向/dev/ttys001設備去寫.?
對應kernel的代碼:

/** ttwrite (LDISC)** Process a write call on a tty device.** Locks:	Assumes tty_lock() is held prior to calling.*/
int
ttwrite(struct tty *tp, struct uio *uio, int flag)

終端會在tty有數據的時候,把數據畫到屏幕上. (注意: printf后面的字符串是終端進程畫到屏幕上的,不是hello畫的,因為hello只是寫文件,寫文件當然不一定會顯示到屏幕, 只是一般腦袋瓜子正常的終端都會回顯對應的文本信息).

至于,如何把一段文本畫到屏幕上,這個就不用多說了.

?


微風不燥,陽光正好,你就像風一樣經過這里,愿你停留的片刻溫暖舒心。

我是程序員小迷(致力于C、C++、Java、Kotlin、Android、Shell、JavaScript、TypeScript、Python等編程技術的技巧經驗分享),若作品對您有幫助,請關注、分享、點贊、收藏、在看、喜歡,您的支持是我們為您提供幫助的最大動力。

歡迎關注。助您在編程路上越走越好!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/37295.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/37295.shtml
英文地址,請注明出處:http://en.pswp.cn/web/37295.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

WordPress中文網址導航欄主題風格模版HaoWa

模板介紹 WordPress響應式網站中文網址導航欄主題風格模版HaoWa1.3.1源碼 HaoWA主題風格除行為主體導航欄目錄外&#xff0c;對主題風格需要的小控制模塊都開展了敞開式的HTML在線編輯器方式的作用配備&#xff0c;另外預埋出默認設置的編碼構造&#xff0c;便捷大伙兒在目前…

入門JavaWeb之 JDBC 連接數據庫

JDBC&#xff1a;Java Database Connectivity&#xff0c;Java 數據庫連接 需要 jar 包支持&#xff1a; java.sql javax.sql mysql-connector-java&#xff08;連接驅動&#xff0c;必須導入&#xff09; 在 MySQL 先建個 jdbc 數據庫后 USE jdbc; 執行后再 CREATE TABLE…

15- 22題聚合函數 - 高頻 SQL 50 題基礎版

目錄 1. 相關知識點2. 例子2.15 - 有趣的電影2.16 - 平均售價2.17 - 項目員工 I2.18 - 各賽事的用戶注冊率2.19 - 查詢結果的質量和占比2.20 - 每月交易 I2.21 - 即時食物配送 II2.22 - 游戲玩法分析 IV 1. 相關知識點 函數 函數含義order by排序group by分組between 小值 an…

Chrome備份數據

Chrome備份數據 1、 導出谷歌瀏覽器里的歷史記錄 參考&#xff1a;https://blog.csdn.net/qq_32824605/article/details/127504219 在資源管理器中找到History文件&#xff0c;文件路徑&#xff1a; C:\Users\你的電腦用戶名\AppData\Local\Google\Chrome\User Data\Default …

堆排序思想分享

人不走空 &#x1f308;個人主頁&#xff1a;人不走空 &#x1f496;系列專欄&#xff1a;算法專題 ?詩詞歌賦&#xff1a;斯是陋室&#xff0c;惟吾德馨 目錄 &#x1f308;個人主頁&#xff1a;人不走空 &#x1f496;系列專欄&#xff1a;算法專題 ?詩詞歌…

丟失的數字(MissNumber)

丟失的數字 給定一個包含 [0, n] 中 n 個數的數組 nums &#xff0c;找出 [0, n] 這個范圍內沒有出現在數組中的那個數。 示例 1&#xff1a; 輸入&#xff1a;nums [3,0,1] 輸出&#xff1a;2 解釋&#xff1a;n 3&#xff0c;因為有 3 個數字&#xff0c;所以所有的數字都…

五、Pentium 微處理器保護模式存儲管理,《微機系統》第一版,趙宏偉

一、分段存儲管理 Pentium支持分段存儲管理、分頁存儲管理和段頁式存儲管理。 1.1 分段存儲管理的基本思想 一個程序由多個模塊組成。 每一個模塊都是一個特定功能的獨立的程序段。 段式管理&#xff1a;把主存按段分配的存儲管理方式。 程序模塊→段→段描述符→段描述符…

【設計】在Java后端開發時使用JSONObject完全替代JAVABean(DTO,VO)是否可行?

其實這樣做你是得不償失&#xff0c;不過也要看什么項目&#xff0c;如果你的項目只在只需要實現功能&#xff0c;不在乎健壯性&#xff0c;可持續性那就完全可以。因為我現在公司老項目所有用的POJO的地方都是用JSONObject。代碼可讀性幾乎為0。你用了可能喪失以下功能&#x…

【微服務】后臺管理項目多數據源管理方案實戰

目錄 前言 1、使用Spring提供的AbstractRoutingDataSource 2、使用MyBatis注冊多個SqlSessionFactory 3、使用dynamic-datasource框架 前言 Java后臺使用MyBatis-plus 快速訪問多個數 據源&#xff0c;這里分享三種常用的多數據源管理方案 1、使用Spring提供的AbstractRout…

【C++深度探索】繼承機制詳解(一)

hello hello~ &#xff0c;這里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;歡迎大家點贊&#x1f973;&#x1f973;關注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;個人主頁&#xff1a;大耳朵土土垚的博客 &#x1…

代碼托管服務:GitHub、GitLab、Gitee

目錄 引言GitHub&#xff1a;全球最大的代碼托管平臺概述功能特點適用場景 GitLab&#xff1a;一體化的開發平臺概述功能特點適用場景 Gitee&#xff08;碼云&#xff09;&#xff1a;中國本土化的代碼托管服務概述功能特點適用場景 功能對比結論 引言 在現代軟件開發中&#…

numpy - array(3)

arr1 np.array([[(1000, 1001, 1002, 1003), (1010, 1011, 1012, 1013), (1020, 1021, 1022, 1023)],[(1100, 1101, 1102, 1103), (1110, 1111, 1112, 1113), (1120, 1121, 1122, 1123)]], dtypeint) (1) 根據坐標訪問元素或內容,更改訪問的內容&#xff0c;array也會更改。“…

C++操作系列(一):MinGW環境安裝與配置(無報錯版)

本文選擇MinGW作為安裝對象。 1. 下載MinGW 進入官網&#xff1a;MinGW - Minimalist GNU for Windows download | SourceForge.net 點擊File&#xff1a; 劃到最下面&#xff1a; &#xfeff; Windows 64位系統下載seh結尾的安裝包&#xff1a; 2. 安裝MinGW 解壓MinGW&am…

力扣第218題“天際線問題”

在本篇文章中&#xff0c;我們將詳細解讀力扣第218題“天際線問題”。通過學習本篇文章&#xff0c;讀者將掌握如何使用掃描線算法和堆來解決這一問題&#xff0c;并了解相關的復雜度分析和模擬面試問答。每種方法都將配以詳細的解釋&#xff0c;以便于理解。 問題描述 力扣第…

【CSS】深入理解CSS 的steps()函數

在CSS動畫中&#xff0c;steps()函數是一個時間函數&#xff0c;它主要用于animation-timing-function屬性&#xff0c;以定義動畫的步進方式。當你想要動畫的某個屬性&#xff08;如background-position或transform: translateX()&#xff09;在特定的步驟之間變化時&#xff…

探索 ES6:現代 JavaScript 的新特性

隨著 JavaScript 的不斷演進&#xff0c;ECMAScript 2015&#xff08;簡稱 ES6&#xff09;作為 JavaScript 的一次重大更新&#xff0c;帶來了許多新特性和語法改進&#xff0c;極大地提升了開發體驗和代碼質量。本文將詳細介紹 ES6 的主要新特性&#xff0c;并展示如何在實際…

NLTK:原理與使用詳解

文章目錄 NLTK簡介NLTK的核心功能1. 文本處理2. 詞匯處理3. 語法分析4. 語義分析5. 情感分析 NLTK的使用1. 安裝NLTK2. 導入NLTK庫3. 下載NLTK數據集4. 文本處理示例5. 情感分析示例 總結 NLTK簡介 NLTK是一個開源的Python庫&#xff0c;用于處理和分析人類語言數據。它提供了…

扛鼎中國AI搜索,天工憑什么?

人類的創作不會沒有瓶頸&#xff0c;但AI的熱度可不會消停。 大模型之戰依舊精彩&#xff0c;OpenAI選擇在Google前一天舉行發布會&#xff0c;兩家AI企業之間的拉扯賺足了熱度。 反觀國內&#xff0c;百模大戰激發了大家對于科技變革的熱切期盼&#xff0c;而如今行業已逐漸…

【操作系統期末速成】 EP01 | 學習筆記(基于五道口一只鴨)

文章目錄 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;??????1.1 考點一&#xff1a;操作系統的概率及特征 三、總結&#xff1a;&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&#x1f680; ?? 回報不在行動…

文章浮現之單細胞VDJ的柱狀圖

應各位老師的需求復現一篇文章的中的某個圖 具體復現圖5的整個思路圖&#xff0c;這里沒有原始數據&#xff0c;所以我使用虛擬生產的metadata進行畫圖 不廢話直接上代碼&#xff0c;先上python的代碼的結果圖 import matplotlib.pyplot as plt import numpy as np# 數據&#…