二三、編譯器

来源:https://www.cnblogs.com/piaolaipiaoqu/archive/2023/12/23/17923864.html
-Advertisement-
Play Games

二三、編譯器 1、One Definition Rule 1)轉化單元 我們寫好的每個源文件(.cpp,.c)將其所包含的頭文件(#include <xxx.h>)合併後,稱為一個轉化單元。 編譯器單獨的將每一個轉化單元生成為對應的對象文件(.obj),對象文件包含了轉化單元的機器碼和轉化單元的引用 ...


二三、編譯器

1、One Definition Rule

1)轉化單元

我們寫好的每個源文件(.cpp,.c)將其所包含的頭文件(#include <xxx.h>)合併後,稱為一個轉化單元。

編譯器單獨的將每一個轉化單元生成為對應的對象文件(.obj),對象文件包含了轉化單元的機器碼和轉化單元的引用信息(不在轉化單元中定義的對象)。

最後鏈接器將各個轉化單元的對象文件鏈接起來,生成我們的目標程式。

比如在對象文件A中包含了定義在其它轉化單元的引用,那麼就去其它轉化單元的對象文件中尋找這個引用的定義來建立鏈接,如果在所有的對象文件中都找不到這個定義,那麼就會生成一個鏈接錯誤。

2)未定義行為

在編寫代碼中,C++標準未做規定的行為,稱為未定義行為,未定義行為的結果是不確定的,具體不同的編譯器下會有不同的效果,比如

c=2*a++ + ++a*6;

這裡先算a++還是先算++a就是一個未定義行為,比如

int x = -25602;

x= x>>2;

x的結果在不同的編譯器下是不確定的,因為這也屬於未定義行為

3)One Definition Rule(ODR)

ODR是一系列規則,而不是一個規則,程式中定義的每個對象都應有著自己的規則;但是基本上來講任何的變數、函數、類、枚舉、模板、概念(C++20)在每個轉化單元中都只允許有一個定義;

在整個程式中,非inline的函數或變數(C++17),有且僅能有一個定義

const聲明的變數或函數只在當前的源文件中有效,可以在一個項目的不同源文件定義相同的const變數

4)名稱的鏈接屬性

程式中的變數、函數、結構等都有著自己的名字,這些名字具有不同的鏈接屬性,鏈接器就是根據這些鏈接屬性來把各個對象文件鏈接起來的。鏈接屬性分為以下三種

①內部鏈接屬性:該名稱僅僅在本轉化單元中有效,如static、const聲明的變數、函數

②外部鏈接屬性:該名稱在其它轉化單元中也有效。通過extern關鍵字可以定義外部鏈接屬性

③無鏈接屬性:該名稱僅僅能夠用於該名稱的作用域內訪問

註:static變數或函數在自己的轉化單元有著自己的記憶體空間,而inline定義的變數只有一個記憶體地址

2、#define

1)用法一

#define A B          //將標識符A定義為B的別名

#define 整數 int        //將整數替換為int
整數 a{};
//#define實際用法
#include <iostream>
#define _HHHH_ int a  //將_HHHH_ 替換為int a
#define VERSION "V2.0"
int main()
{
	_HHHH_ { 250 };
	std::cout << a << std::endl;
	std::cout << VERSION<<std::endl;
}

2)C++中定義常量的方式

//C++定義常量的方法
const int width{1080};

//C語言中經常通常#define來定義常量
#define width 1080
#define的方式來定義常量存在一個問題,有時候並不安全

3)#define其它寫法

#define H  //定義一個標識符H ,代碼中的H將會被刪除掉
int H a 相當於 int a;

//實際場景應用
#define _in_            //沒有實際意義
#define _out_ 
int ave(_in_ int a,_out_ int& b)
{
    return a+b
}

4)取消巨集的定義

//語法
#undef H   //

//應用場景
#define _H_
#undef _H_    //刪除巨集_H_的定義,後面的代碼不能使用

註:執行順序為代碼編譯的順序(從上到下),而不是函數調用的順序

5)定義複雜表達式巨集

#define SUM(X,Y) X+Y                 //使用X+Y替換SUM(X,Y)
#define AVE(X,Y) (X+Y)/2             //使用(X+Y)/2替換AVE(X,Y)
#define BIGGER(X,Y) ((X)>(Y)?(X):(Y))

SUM(100,200);
AVE(100,200);
BIGGER(100,200);

//實際應用場景
#define RELEASE(x) delete[] x;x=nullptr

int main()
{
    int* a  = new int[50];
    RELEASE(a);
}

6)定義複雜表達式巨集

// #可以將一個標識符參數字元串化
#define SHOW(X) std::cout<<#X          //通過#將X處理成了字元串
SHOW(1234fg);      //相當於std::cout<<"12345fg"

// ##可以連接兩個標識符
#define T1(X,Y) void X##Y(){std::cout<<#Y;}
T1(test, 22);
3、namespace

有時候為了方便管理,把相關的函數、變數、結構體等會附加到一個命名空間中

//聲明命名空間
namespace t
{
    int value;
}
//訪問這個命名空間的變數
t::value

//直接使用命名空間,不推薦
using namespace t;
vlaue=255;

2)全局命名空間

雖有具有鏈接屬性的對象,只要沒有定義命名空間,就預設定義在全局命令空間中,全局命名空間中成員的訪問不用顯示的指定,當局部名稱覆蓋了全局名稱時才需要顯示的指定全局命令空間

int a;
::a=250;

3)命名空間的擴展

//第二個htd屬於對htd命名空間的擴展,weight和heigth同屬於一個命名空間
namespace htd
{
    int weigth{1980};
}
namespace htd
{
    int heigth{1080};
}

4)命名空間的聲明

//htd.h
namespace std
{
    extern int height;      //變數聲明
    void test();            //函數聲明
}

//htd.cpp
#include <iostream>
#include "htd.h"

int htd::heigth{250};      //變數定義
void htd::test()          //函數定義
{
    std::cout<<htd::height;
}

5)命名空間的嵌套

//htd.h
namespace htd
{
    namespace hack               //命名空間的嵌套
    {
        void hackServer();
    }
    
}

//htd.cpp
void htd::hack::hackServer()
{
    ...
}
void htd::sendSms()
{
    ...
}

6)未命名的命名空間

​ 不給命名空間指定名稱,將會聲明一個未命名的命名空間。未命名的命名空間中聲明的內容一律為內部鏈接屬性,包括extern聲明的內容,未命名的命名空間僅僅在本轉化單元中有效

//t.cpp
void THack()
{
    
}
//x.cpp
namespace
{
    void THack()
	{
    
	}
}	
int main()
{
    THack();
}
//

7)命名空間的別名

namespace htd
{
    void sendSms();
    namespace hack
    {
        void hackServer();
    }
}
namespace hServer=htd::hack;
hServer::hackServer();
4、預處理指令邏輯

所有#開頭的代碼都是和編譯器進行打交道

1)#ifdef

#define _HEIGHT_ 1080       //#ifdef和#endif成對出現
#ifdef _HIGHT_
#else
#endif


//hc.h
#ifdef _HC_        //如果定義了巨集_HC_,就執行XXXX裡面的代碼。如果沒有定義,則執行YYYYY
XXXX
#else
YYYYY
#endif

//常見用法
#ifndef _HC_     //如果沒有定義了巨集_HC_
#define _HC_     //則定義了巨集_HC_
#else
#endif
//實際應用場景
#ifdef UNICODE     //如果使用了UNICODE字元集,則使用wchar_t定義變數
wchar_t a;
#else
char a;
#endif

//通過預處理指令進行版本控制
#define VERSION 101

#if VERSION==100           //當VERSION為100時,執行如下邏輯,否則執行else中的邏輯
	void SendSms()
    {
        
    }
#else
	void SendSms()
    {
        
    }
#endif 

2)#elif

//#elif語法
#define _HEIGHT_ 2080
#if _HEIGHT_ == 1080          //針對每一個解析度執行不同的邏輯
...
#elif _HEIGHT_ == 720
...
#else
...
#endif
    
//預處理指令可以進行簡單的計算//當VERSION為100時,執行如下邏輯,否則執行else中的邏輯
	void SendSms()
    {
        
    }
5、預定義巨集

1)標準預定義標識符_fun_

編譯器支持ISO C99和ISO C++ 11,即可使用該預定義標識符。用於返回函數的名稱

__func__   //返回函數的名稱

//用法示例
#include <iostream>

int main()
{
	std::cout << __func__ << std::endl;     //返回函數名稱,輸出main
}

2)標準預定義巨集

編譯器支持ISO C99和ISO C++17標準,即可使用以下預定義巨集

巨集 說明
_DATE_ 返回源文件的編譯日期
_TIME_ 返回當前轉化單元的轉化時間(可理解為代碼修改的時間)
_FILE_ 返回源文件的名稱
_LINE_ 返回當前的行
__cplusplus 噹噹前單元為C++時(即.cpp文件時),__cplusplus定義為一個整數,否則不是c++文件
#include <iostream>

int main()
{
	std::cout << __func__ << std::endl;     //返回函數名稱,輸出main
	std::cout << __DATE__ << std::endl;
	std::cout << __TIME__ << std::endl;
	std::cout << __FILE__ << std::endl;
	std::cout << __LINE__ << std::endl;
	std::cout << __cplusplus << std::endl;
}

3)MSVC的預定義巨集(可理解為微軟的VC編譯器中,預定義的一些巨集)

巨集 說明
_CHAR_UNSIGNED 如果char類型為無符號,該巨集定義為1,否則為未定義
_COUNTER_ 用於計數,從0開始,每使用一次都會遞增1
_DEBUG 如果設置了_DEBUG巨集,代表當前為調試狀態/lDd /mDd /mTd該巨集定義為1,否則為未定義
_FUNCTION_ 返回函數名稱 ,但是不包含修飾名
_FUNCDNAME_ 函數名稱 包含修飾名
_FUNCSIG_ 包含了函數簽名的函數名
_WIN32 當編譯為32位ARM,64位ARM,X68或X64定義為1,否則未定義
_WIN64 當編譯為64位ARM或x64定義為1,否則未定義。用於區別
_TIMESTAMP_ 最後一次源代碼修改的時間和日期
#include <iostream>

int main()
{
#ifdef _CHAR_UNSIGNED  //如果char為無符號類型,可以通過該預定義巨集進行檢驗
	std::cout << "無符號類型";
#endif
	std::cout << "有符號類型" << std::endl;
	std::cout << __COUNTER__ << std::endl;   //代表計數
	std::cout << __COUNTER__ << std::endl;
	std::cout << __COUNTER__ << std::endl;
#ifdef _DEBUG       //代表調試狀態
	std::cout << "調試狀態" << std::endl;
	std::cout << __FUNCTION__ << std::endl;  //返回函數名,不包含修飾名
	std::cout << __FUNCDNAME__ << std::endl; //返回函數名,包含修飾名
	std::cout << __FUNCSIG__ << std::endl;  //包含函數簽名,即調用、約定等信息
	std::cout << __TIMESTAMP__ << std::endl; //最後一次源代碼修改的時間和日期

#endif
#ifdef _WIN32  //用於區分Win32架構或者Win64架構
	std::cout << "X86" << std::endl;
#endif // _WIN32

}

只要【項目屬性】-【C/C++】-【代碼生成】-【運行庫】,選擇了調試,則_DEBUG就會顯示

註:上述巨集只在微軟的VS編輯器才可以使用,其它的編譯器無法使用

6、調試

​ 我們編寫好程式以後,可能存在一些bug和錯誤,對於語法上錯誤,編譯器能夠直接給出提示,而對於邏輯上的錯誤,編譯器不能夠直接發現,調試就是一個找錯誤和改錯誤的過程

1)調試建議:為了方便調試,在編程風格上提出如下建議

①功能能模塊化就模塊化

②使用能夠體現出具體意義的函數名和變數名

③使用正常的縮進和代碼塊

④良好的註釋習慣

2)利用集成調試器調調試程式

VS2019繼承了一個調試器,可以利用斷點、流程跟蹤等方式來調試自己的程式

斷點就是當程式執行到斷點位置,程式就會停下來

3)利用其它調試器

OllyDbg

X96Dbg

WinDbg

4)利用預處理指令來輸出調試信息

#define _dbg_i    //先定義一個巨集

#ifdef _dbg_i    //如果巨集存在,則執行下麵的代碼塊
	std::cout<<"調試信息";
#endif
7、assert

assert巨集需要頭文件cassert

1)assert語法

//assert語法
assert(bool表達式);   //如果括弧內的bool表達式為false,則會調用std::abort()函數,彈出下麵的對話框,

2)關閉assert

//關閉assert
#define NDEBUG  //可以在當前轉化單元關閉assert,但是這個定義必須放在#include <cassert>之前

3)static_assert(靜態斷言)

//static_assert用於編譯時檢查條件
static_assert(bool表達式,"Error information");   //先檢查表達式,若表達式為假,則輸出後面的錯誤信息。如果表達式為0,則程式是不進行編譯的,此處二點bool表達式只能用於常量

//C++17新語法
static_assert(bool表達式);

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

-Advertisement-
Play Games
更多相關文章
  • 目錄Hive集成表引擎創建表使用示例如何使用HDFS文件系統的本地緩存查詢 ORC 輸入格式的Hive 表在 Hive 中建表在 ClickHouse 中建表查詢 Parquest 輸入格式的Hive 表在 Hive 中建表在 ClickHouse 中建表查詢文本輸入格式的Hive表在Hive 中建 ...
  • 一、異常信息描述 執行資料庫操作時,主鍵id沒有自增,且報“more than one owned sequence found”的異常,造成數據沒有insert進去,下麵是詳細的異常信息: java.lang.reflect.InvocationTargetException at sun.ref ...
  • 本文深入探討了安卓DocumentsProvider的應用場景,分析了其優勢與不足,並提供了簡單的代碼實現。DocumentsProvider是安卓系統中用於文件存儲與訪問的關鍵組件,為應用開發者提供了強大的文件管理能力。 ...
  • Android對接微信登錄記錄 - Stars-One的雜貨小窩 Android項目要對接下微信登錄,稍微記錄下踩坑點 代碼 1.添加依賴 implementation 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.0' 2.聲明Activity 在你 ...
  • login-status-iframe.html是keycloak為我們提供的一種檢測用戶登錄狀態的頁面,它要求用戶對接的系統通過iframe進行嵌入,然後通過window.addEventListener去訂閱子頁面的信息。 提示: 所有 HTML DOM 事件,可以查看我們完整的https:// ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 最近受夠了公司內部站點每次登陸都需要填寫用戶名和密碼,還有輸入驗證碼。 要是能夠直接跳過登陸頁面就好啦。 說乾就乾,決定使用油猴插件實現自動登陸功能。 其中最難解決的就是驗證碼破解,花了一天的時間完美解決,現在整理出來。 1.分析驗證碼 ...
  • 實現說明: 在 JS 中 canvas 原生沒有支持對文字間距的調整,我們可以通過將文字的每個字元單獨渲染來實現。本案例從 CanvasRenderingContext2D 對象的原型鏈上擴展了一個用於繪製帶間距的函數 fillTextWithSpacing(),使用方式與原生 fillText() ...
  • 1.關鍵字(keyword) 定義:被Java語言賦予了特殊含義,用做專門用途的字元串(或單詞),這些字元串(或單詞)已經被Java定義好了。 特點:全部關鍵字都是小寫字母。 關鍵字查閱的官方地址: https://docs.oracle.com/javase/tutorial/java/nutsa ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...