C語言進階--程序的編譯(預處理動作)+鏈接

1.程序的翻譯環境和執行環境

在ANSI C標準的任何一種實現中,存在兩種不同的環境。

第一種是翻譯環境:將源代碼轉換為可執行的機器指令(0/1);

第二種是執行環境:用于實際執行代碼。

2.詳解編譯+鏈接

2.1翻譯環境

程序編譯過程:

在這里插入圖片描述

linux系統中的編譯器gcc生成的目標文件:xxx.o

(不同的參數)

2.2編譯本身也分為幾個階段

在這里插入圖片描述
預編譯/預處理:(文本操作)

  • 頭文件的包含(#include)
  • #define定義符號的替換,刪除定義的符號
  • 刪除注釋
  • ···

編譯:把C語言代碼轉換成匯編代碼(包括語法分析、詞法分析、符號匯總、語義分析)

匯編:把匯編代碼轉換成二進制指令(機器指令);將編譯時的符號匯總形成符號表

鏈接:合并段表;符號表的合并和重定位

gcc test.c -E -o test.ivim /usr/include/stdio.h    

在這里插入圖片描述

gcc test.c -E -o test.i

在這里插入圖片描述

gcc test.i -S  
//生成test.s

符號匯總:

在這里插入圖片描述

gcc test.s -c
//生成test.o目標文件(二進制文件)

在這里插入圖片描述
Linux下的可執行程序是elf格式。

2.3運行環境

程序執行的過程:

  • 程序必須載入內存。在有操作系統的環境中,一般由操作系統完成。在獨立的環境中,程序的載入必須由手工安排,也可能是通過可執行代碼植入只讀內存來完成。
  • 程序的執行開始,接著調用main函數。
  • 開始執行程序代碼。這時程序將使用一個運行時堆棧(stack),存儲函數的局部變量和返回地址。程序同時也可以使用靜態(static)內存,存儲于靜態內存中的變量在程序的整個執行過程中一直保留它們的值。
  • 終止程序。正常中止main函數;也可能是意外終止。

3.預處理詳解

3.1預定義符號

__FILE__  //進行編譯的源文件
__LINE__  //文件當前的行號
__DATE__  //文件被編譯的日期
__TIME__  //文件被編譯的時間
__STDC__  //如果編譯器遵循ANSI C標準,其值為1,否則未定義(gcc編譯器是遵循ANSI C標準的)    

這些預定義符號都是語言內置的。

#include <stdio.h>
int main()
{int i = 0;FILE* pf = fopen("log.txt", "w");if (pf == NULL){perror("fopen");return EXIT_FAILURE; //1//EXIT_SUCCESS; //0}for (i = 0; i < 10; i++){fprintf(pf, "file:%s line=%d date:%s time:%s i=%d", __FILE__, __LINE__, __DATE__, __TIME__, i);}fclose(pf);pf = NULL;return 0;
}

在這里插入圖片描述

3.2#define

3.2.1#define定義標識符(不加分號)
#define name stuff
#include <stdio.h>#define MAX 1000
#define STR "hello bit"int main()
{int m = MAX;printf("%d\n", MAX); //1000printf("%s\n", STR); //hello bitreturn 0;
}

續行符
在這里插入圖片描述

3.2.2#define定義宏

#define機制包括了一個規定,允許把參數替換到文本中,這種實現通常稱為宏(macro)或定義宏(define macro)。

#define name(parament-list) stuff

其中的parament-list是一個由逗號隔開的符號表,它們可能出現在stuff中。

注意:參數列表的左括號必須與name緊鄰。如果兩者之間有任何空白存在,參數列表會被解釋為stuff的一部分。

#include <stdio.h>#define SQUARE(X) X*Xint main()
{int r = SQUARE(5); printf("%d\n", r); //25return 0;
}

上面寫法有缺陷eg:

#include <stdio.h>#define SQUARE(X) X*Xint main()
{int r = SQUARE(5+1);  //5 + 1 * 5 + 1printf("%d\n", r); //11return 0;
}

修正:不吝嗇括號

#include <stdio.h>#define SQUARE(X) ((X)*(X))int main()
{int r = SQUARE(5+1);  //((5 + 1) * (5 + 1))printf("%d\n", r); //36return 0;
}
3.2.3#define替換規則

在程序中擴展#defien定義符號和宏時,需要涉及幾個步驟:

  • 在調用宏時,首先對參數進行檢查,看看是否包含任何由#defien定義的符號。如果是,它們首先被替換。
  • 替換文本隨后被插入到程序中原來文本的位置。對于宏,參數名被它們的值所替換。
  • 最后,再次對結果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復上述處理過程。

注意:

宏參數和#define定義中可以出現其它#define定義的符號。但是對于宏,不能出現遞歸。

當預處理器搜索#define定義的符號時,字符串常量的內容并不被搜索。

#include <stdio.h>#define M 100
#define DOUBLE(X) ((X)+(X))int main()
{DOUBLE(M+2);  //(100+2)//((100+2)+(100+2))return 0;
}
3.2.4#和##

如何把參數插入到字符串中?
在這里插入圖片描述

#include <stdio.h>#define PRINT(N) printf("the value of "#N" is %d\n", N)
int main()
{int a = 10;PRINT(a); //printf("the value of ""a"" is %d\n", a);int b = 10;PRINT(b);return 0;
}

在這里插入圖片描述

#include <stdio.h>#define PRINT(N, FORMAT) printf("the value of "#N" is "FORMAT"\n", N)
int main()
{int a = 10;PRINT(a, %d); int f = 3.14f;PRINT(f, %lf);return 0;
}

在這里插入圖片描述
##可以把位于它兩邊的符號合成一個符號。它允許宏定義從分離的文本片段創建標識符。

#include <stdio.h>#define CAT(Class, Num) Class##Num
int main()
{int Class106 = 100;printf("%d\n", CAT(Class, 106)); //100//printf("%d\n", Class106);return 0;
}
3.2.5帶副作用的宏參數

當宏參數在宏的定義中出現超過一次時,如果參數帶有副作用,那么你在使用這個宏的時候就可能出現危險,導致不可預測的后果。副作用就是表達式求值時出現的永久性效果。

#include <stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 5; //6 7int b = 4; //5int m = MAX(a++, b++); //int m = ((a++)>(b++)?(a++):(b++));6      5   > 4   ?6printf("m=%d ", m); //6printf("a=%d b=%d\n", a, b); //7 5return 0;
}
3.2.6宏和函數對比

宏通常被應用于執行簡單的運算。

eg:在2個數中找出較大的一個,為什么不用函數來完成這個任務呢?

原因:

1.用于調用函數和從函數返回的代碼可能比實際執行這個小型計算工作所需要的時間更多。所以宏比函數在程序的規模和速度方面更勝一籌。

2.更為重要的是函數的參數必須聲明為特定的類型。所以函數只能在類型合適的表達式上使用。反之宏可以適用于整型、長整型、浮點型等可以>來比較的類型。宏是與類型無關的。

函數調用(參數傳參、棧幀創建)——>計算——>函數返回

宏的缺點:

1.每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長度。

2.宏是沒法調試的。

3.宏由于類型無關,也就不夠嚴謹。

4.宏可能會帶來運算符優先級的問題,導致程序容易出錯。

宏有時候可以做函數做不到的事情。比如宏的參數可以出現類型,但是函數不可以。

#define MALLOC(num, type) (type*)malloc((num)*sizeof(type))int main()
{//malloc(40);//malloc(10, int); //errorint* p = (int*)MALLOC(10, int);//int* p = (int*)malloc((10)*sizeof(int));return 0;
}
屬性#define定義宏函數
代碼長度每次使用時,宏代碼都會被插入到程序中。除了非常小的宏除外,程序的長度會大幅度增長函數代碼只出現于一個地方。每次使用這個函數時,到調用那個地方
執行速度更快存在函數的調用和返回的額外開銷,所以相對慢一些
操作符優先級宏參數的求值是在所有周圍表達式的上下文環境里,除非加上括號,否則鄰近操作符的優先級可能會產生不可預料的后果,所以建議宏在書寫時多些括號函數參數只在函數調用時求值一次,它的結果值傳遞給函數。表達式的求值結果更容易預測
帶有副作用的參數參數可能被替換到宏體中的多個位置,所以帶有副作用的參數求值可能會產生不可預料的結果函數參數只在傳參的時候求值一次,結果更容易控制
參數類型宏的參數與類型無關,只要對參數的操作是合法的,它就可以適用于任何參數類型函數的參數是與類型有關的,如果參數的類型不同,就需要不同的函數,即使它們執行的任務是相同的
調試宏是不方便調試的函數是可以逐語句/逐過程調試的
遞歸宏是不能遞歸的函數是可以遞歸的
3.2.7命名約定

一般來講函數和宏的使用語法很相似。所以語言本身沒法幫我們區分二者。那我們平時的習慣是:把宏名全部大寫;函數名不要全部大寫

3.2.8#undef

這條指令用來移除一個宏定義。

#undef name
#define M 100#include <stdio.h>int main()
{printf("%d\n", M); //100
#undef M    printf("%d\n", M); //errorreturn 0;
}

3.3命令行定義

linux下gcc編譯器實現:

在這里插入圖片描述
編譯指令

gcc test.c -D SZ=10

3.4條件編譯

在編譯一個程序的時候我們如果要將一條語句(一組語句)編譯或放棄是很方便的。因為我們由條件編譯指令。

在這里插入圖片描述
常見的條件編譯指令:

#if 常量表達式//···
#endif
#include <stdio.h>#if 0
int main()
{printf("hehe\n");return 0;
}
#endif

在這里插入圖片描述
2.多個分支的條件編譯

#if 常量表達式//···
#elif 常量表達式//···
#else 常量表達式//···
#endif
#define MAX 3#include <stdio.h>int main()
{
#if M<5printf("hehe\n");
#elif M==5 printf("haha\n");
#elseprintf("heihei\n");
#endifreturn 0;
}

預編譯代碼:

在這里插入圖片描述
3.判斷是否被定義

#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol
//#define MAX 100#include <stdio.h>int main()
{
#if !defined(MAX)printf("max\n");
#endifreturn 0;
}
#define MAX 100#include <stdio.h>int main()
{
#ifdef MAXprintf("max\n");
#endifreturn 0;
}
//#define MAX 100#include <stdio.h>int main()
{
#ifndef MAXprintf("max\n");
#endifreturn 0;
}

4.嵌套指令

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

3.5文件包含

防止頭文件被多次重復的包含。

方案1:

#ifndef __TEST_H__
#define __TEST_H__
···
#endif    

在這里插入圖片描述
方案2:

#pragma once

在這里插入圖片描述

#include <stdio.h> //庫文件包含
#include "test.h" //本地文件包含

<>和""的區別:查找的策略不同。

<>查找策略:直接去庫目錄下查找;

""查找策略:先去代碼所在的路徑下查找;如果找不到,再去庫目錄下查找。

Linux環境的標準頭文件的路徑:

/usr/include

VS環境的標準頭文件的路徑:(不同版本有差異)

C:\Program Files(x86)\Microsoft Visual Studio 12.0\VC\include
//這是VS2013的默認路徑

4.其他預處理指令

#error
#pragma
#line
···
不做介紹,自己了解,參考《C語言深度解剖》
#pragma pack()在結構體部分介紹。    

百度筆試題(offsetof宏的實現)

寫一個宏,計算結構體中某變量相對于首地址的偏移,并給出說明。
在這里插入圖片描述

#include <stdio.h>
#include <stddef.h>struct S
{char c1;int i;char c2;
};
int main()
{struct S s = {0};printf("%d\n", offsetof(struct S, c1)); //0printf("%d\n", offsetof(struct S, i)); //4printf("%d\n", offsetof(struct S, c2)); //8return 0;
}
#define OFFSETOF(type, m_name)  (size_t)&(((type*)0)->m_name)
#include <stdio.h>struct S
{char c1;int i;char c2;
};
int main()
{struct S s = {0};printf("%d\n", OFFSETOF(struct S, c1)); //0printf("%d\n", OFFSETOF(struct S, i)); //4printf("%d\n", OFFSETOF(struct S, c2)); //8return 0;
}

總結

今天就暫且更新至此吧,期待下周再會。如有錯誤還請不吝賜教。希望對您學習有所幫助,翻頁前留下你的支持,以防下次失蹤了嗷。

作者更新不易,免費關注別手軟。

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

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

相關文章

微調大模型:什么時候該做,什么時候不該做?

目錄 一、什么是“微調”&#xff1f;你真的需要它嗎&#xff1f; 二、什么時候不該微調&#xff1f; &#x1f6ab; 不該微調的 5 個典型場景&#xff1a; 1. 通用問答、閑聊、常識類內容 2. 企業內部問答 / 文檔助手 3. 想要通過微調“學會格式” 4. 沒有大量高質量標…

微深節能 碼頭裝卸船機定位與控制系統 格雷母線

微深節能碼頭裝卸船機定位與控制系統&#xff1a;格雷母線技術賦能港口作業智能化升級 在現代化港口散貨裝卸作業中&#xff0c;裝卸船機是連接船舶與陸域運輸的核心樞紐設備。傳統裝卸船機依賴人工操作&#xff0c;存在定位偏差大、動態協同難、安全風險高等痛點。微深節能基于…

如何檢查popover氣泡組件樣式?調試懸停元素CSS樣式的解決方案

1. 問題 當我們要檢查這種彈出層的CSS樣式時&#xff0c;會發現特別棘手&#xff0c;因為鼠標移走就消失了。如果是display:none控制的&#xff0c;可能還能找到&#xff0c;如果是用js通過v-if控制的&#xff0c;就無法調試了。 2. 解決方案 使用 setTimeout debugger 就…

網絡攻防技術一:緒論

文章目錄 一、網絡空間CyberSpace1、定義2、基本四要素 二、網絡空間安全1、定義2、保護對象3、安全屬性4、作用空間 三、網絡攻擊1、攻擊分類2、攻擊過程 四、網絡防護1、定義2、安全模型3、安全服務5類4、特定安全機制8種5、普遍性安全機制5種 五、網絡安全技術發展簡史1、第…

徹底理解Spring三級緩存機制

文章目錄 前言一、Spring解決循環依賴時&#xff0c;為什么要使用三級緩存&#xff1f; 前言 Spring解決循環依賴的手段&#xff0c;是通過三級緩存&#xff1a; singletonObjects&#xff1a;存放所有生命周期完整的單例對象。&#xff08;一級緩存&#xff09;earlySingleto…

【 SpringCloud | 微服務 網關 】

單體架構時我們只需要完成一次用戶登錄、身份校驗&#xff0c;就可以在所有業務中獲取到用戶信息。而微服務拆分后&#xff0c;每個微服務都獨立部署&#xff0c;這就存在一些問題&#xff1a; 每個微服務都需要編寫登錄校驗、用戶信息獲取的功能嗎&#xff1f; 當微服務之間調…

【前端面經】字節跳動一面

寫在前面&#xff1a;面經只是記錄博主遇到的題目。每題的答案在編寫文檔的時候已經有問過deepseek&#xff0c;它只是一種比較普世的答案&#xff0c;要學得深入還是靠自己 Q&#xff1a;三欄布局的實現方式&#xff08;圣杯模型&#xff09;如何實現 A&#xff1a; /* 整個 …

ST-GCN

1.bash 安裝git 在目錄下右鍵使用git bash打開 需要安裝wgetbash download_model.sh&#xff0c;下載.sh文件 wget: command not found&#xff0c;Windows系統使用git命令 下載預訓練權重_sh文件下載-CSDN博客 bash tools/get_models.sh 生成了三個.pt文件

計算機網絡全維度解析:架構協議、關鍵設備、安全機制與新興技術深度融合

計算機網絡作為當今數字化社會的基石&#xff0c;其復雜性和應用廣泛性遠超想象。本文將從基礎架構、協議體系、關鍵設備、安全機制到新興技術&#xff0c;進行全方位、深層次的解析&#xff0c;并輔以實際應用場景和案例分析。 一、網絡架構與分類的深度剖析 1.1 網絡分類的立…

大語言模型的推理能力

2025年&#xff0c;各種會推理的AI模型如雨后春筍般涌現&#xff0c;比如ChatGPT o1/o3/o4、DeepSeek r1、Gemini 2 Flash Thinking、Claude 3.7 Sonnet (Extended Thinking)。 對于工程上一些問題比如復雜的自然語言轉sql&#xff0c;我們可能忍受模型的得到正確答案需要更多…

黑馬程序員C++核心編程筆記--3 函數高級

3.1 函數默認參數 本節內容之前已經整理過&#xff0c;詳見22.函數的默認值 3.2 函數占位參數 C中函數的形參列表里可以有占位參數&#xff0c;用來做占位&#xff0c;調用函數時必須補填該位置 語法&#xff1a; 返回值類型 函數名 (數據類型) {} 在現階段函數的占位參數…

數據倉庫分層 4 層模型是什么?

企業每天都在產生和收集海量數據。然而&#xff0c;面對這些數據&#xff0c;許多企業卻陷入了困境&#xff1a;如何高效管理、處理和分析這些數據&#xff1f;如何從數據中提取有價值的信息來支持業務決策&#xff1f;這些問題困擾著眾多數據分析師和 IT 管理者。 在眾多架構…

Java正則表達式完全指南

Java正則表達式完全指南 一、正則表達式基礎概念1.1 什么是正則表達式1.2 Java中的正則表達式支持 二、正則表達式基本語法2.1 普通字符2.2 元字符2.3 預定義字符類 三、Java中正則表達式的基本用法3.1 編譯正則表達式3.2 創建Matcher對象并執行匹配3.3 常用的Matcher方法 四、…

緩存擊穿、緩存雪崩、緩存穿透以及數據庫緩存雙寫不一致問題

在項目中&#xff0c;我們所需要的數據通常存儲在數據庫中&#xff0c;但是數據庫的數據保存在硬盤上&#xff0c;硬盤的讀寫操作很慢&#xff0c;為了避免直接訪問數據庫&#xff0c;我們可以使用 Redis 作為緩存層&#xff0c;緩存通常存儲在內存中&#xff0c;內存的讀寫速度…

可靈2.1 vs Veo 3:AI視頻生成誰更勝一籌?

在Google發布Veo 3幾天后,可靈顯然感受到了壓力,發布了即將推出的視頻模型系列可靈 2.1的早期體驗版。 據我了解,有三種不同的模式: 可靈 2.1 標準模式: 720p分辨率 僅支持圖像轉視頻(生成更快,一致性更好) 5秒視頻仍需20積分 可靈 2.1 專業模式: 1080p分辨率 僅在圖…

解決Docker存儲空間不足問題

虛擬機磁盤擴展實戰&#xff1a;解決Docker存儲空間不足問題 問題背景 在虛擬機中運行的Linux系統上&#xff0c;Docker服務因根分區空間不足而無法正常運行。初始狀態如下&#xff1a; [rootlocalhost ~]# df -h / 文件系統 容量 已用 可用 已用% 掛載點…

Redis 中如何保證緩存與數據庫的數據一致性?

在 Redis 中保證緩存與數據庫的數據一致性&#xff0c;需結合業務場景選擇以下策略&#xff1a; 核心策略總結 Cache Aside&#xff08;旁路緩存&#xff09;模式 讀操作&#xff1a;先查緩存&#xff0c;未命中則查數據庫并寫入緩存。寫操作&#xff1a;先更新數據庫&#xf…

晶振頻率穩定性:5G 基站與航天設備的核心競爭力

在當今科技飛速發展的時代&#xff0c;電子設備的性能和可靠性至關重要。晶振作為電子設備中的核心部件&#xff0c;為系統提供精確的時間和頻率基準。晶振的頻率穩定性直接影響著設備的整體性能&#xff0c;從日常生活中廣泛使用的智能手機、智能穿戴設備&#xff0c;到對精度…

PDFGear——完全免費且功能強大的PDF處理軟件

關鍵詞 &#xff1a;PDFGear、免費、跨平臺、多功能、OCR 概要 &#xff1a;PDFGear是一款完全免費且功能強大的PDF處理軟件&#xff0c;支持Windows、macOS、iOS和Android等多平臺使用。它集PDF閱讀、編輯、格式轉換、OCR識別及AI智能助手于一體&#xff0c;滿足用戶多樣化文檔…

【筆記】在 MSYS2(MINGW64)中正確安裝 Rust

#工作記錄 1. 環境信息 Windows系統: MSYS2 MINGW64當前時間: 2025年6月1日Rust 版本: rustc 1.87.0 (17067e9ac 2025-05-09) (Rev2, Built by MSYS2 project) 2. 安裝步驟 步驟 1: 更新系統包數據庫并升級已安裝的包 首先&#xff0c;確保我們的 MSYS2 系統是最新狀態。打…