MSVC源碼觀察日記

第n天(n是這篇文章被發表前的學習天數。)

爬源碼的方法跟學習的方法

目前設定從xiosbase開始,如果遇到#include且這個文件沒看過,就跳進去看看,直到爬回xiosbase文件的最後一行。

路徑紀錄

Xiosbase->yvals_core.h->vcruntime.h->sal.h->concurrencysal.h->結束concurrencysal.h->結束sal.h->vadefs.h->vadefs.h結束->vcruntime.h

Preprocessor Macros

RC_INVOKED: Windows 資源編譯器 (Resource Compiler) 編譯 .rc 文件時定義的預處理器標識符

Q_MOC_RUN: 當編譯器運行 MOC 時,通常會定義 Q_MOC_RUN 這個標識符

__midl: 處理接口定義文件(.idl 文件)時,通常會定義 __midl

MIDL_PASS: 用於指示在 MIDL 編譯期間應該將某個參數傳遞給 RPC 過程的客戶端和伺服器代碼。

_PREFAST_:PREfast 是一個由 Microsoft 開發的靜態代碼分析工具

_MSC_EXTENSIONS: _MSC_EXTENSIONS 被定義時,表示編譯器將支援 Microsoft 特有的語言擴展。這包括一些與標準 C++ 不完全兼容的擴展功能。

_USING_V110_SDK71_:using Visual C++ 11.0/12.0 with the Windows 7.1 SDK.

UNDOCKED_WINDOWS_UCRT:無資訊。

_M_CEE_PURE: 指示是否編譯純粹的 Common Language RuntimeCLR)代碼。M_用於指示mircosoft獨有的宏。

_M_CEE: 表示代碼包含 Managed 和非 Managed 部分

_M_ARM: 目標平台是 ARM 架構

_M_ARM64: 目標平台是 ARM64AArch64)架構

_M_IX86: 目標平台是 x86 架構

_M_HYBRID_X86_ARM64: 支持同時在 x86 ARM64 架構上執行的應用程序

_M_ARM64EC: 目標平台是Arm 64-bit with x86 Emulation Compatible架構。

_M_X64: 目標平台是 x6464位元 x86-64,又稱 AMD64 Intel 64

_WIN64: 這個宏用於標識是否正在編譯為 64 位的 Windows 應用程序。(MSVC)特有的宏。

Macro

_STL_COMPILER_PREPROCESSOR

_VCRT_COMPILER_PREPROCESSOR

_UCRT

Header

yvals_core.h

_STL_COMPILER_PREPROCESSOR

vcruntime.h

_VCRT_COMPILER_PREPROCESSOR

_UCRT

__CRTIMP _VCRT_DEFINED_CRTIMP _DLL CRTDLL _CRTBLD __declspec(dllimport)

我們有_DLL  _CRTIMP __declspec(dllimport)

__declspec(dllimport)Microsoft 特有的,指示一個函數或變數是在動態連接庫(DLL)中定義的,而不是在當前編譯單元中。

sal.h

(macro)

__ATTR_SAL

_SAL_VERSION

__SAL_H_VERSION

_USE_DECLSPECS_FOR_SAL:非PREFAST時0

_USE_ATTRIBUTES_FOR_SAL:非PREFAST時0

_SAL1_Source_ (Name, args, annotes)

_SAL1_1_Source_(Name, args, annotes)

_SAL1_2_Source_(Name, args, annotes)

_SAL2_Source_(Name, args, annotes)

_SAL_L_Source_(Name, args, annotes)

_At_(target, annos)

_At_buffer_(target, iter, bound, annos)

_When_(expr, annos)

_Group_(annos)

_GrouP_(annos)

_Success_(expr)

_Return_type_success_(expr)

_On_failure_(annos)

_Always_(annos)

(Preprocessor Macros)

_PREFAST_:

_USING_V110_SDK71_

vadefs.h

vadefs.h是頂層的、廣泛地在CRT的頭文件。

warning堆疊是由編譯器維護的,可pushpop

xxx& 表示xxx是個參考

const_cast<>()是個解const的類型轉換

對齊要求規定了數據在內存中存放的起始位置應該是某個地址的倍數。

__alignof(t)運算符返回獲取類型或表達式的對齊要求

MSVC)編譯器的預定義宏

_M_CEE_PURE: ""的公共語言運行時(CLR)代碼

_M_CEE: 表示代碼包含 Managed 和非 Managed 部分

_M_ARM: 目標平台是 ARM 架構

_M_ARM64: 目標平台是 ARM64AArch64)架構

_M_IX86: 目標平台是 x86 架構

_M_HYBRID_X86_ARM64: 支持同時在 x86 ARM64 架構上執行的應用程序

xkeycheck.h

警告

本文是從Visual Studio 2022、ChatGPT、Stackoverflow,CSDN、MSDN、cppreference一大堆地方抄過來的,相應的,顏色會非常混亂,而且繁簡轉換,我盡量排版好看一點來彌補。

第一+n天

(1)在vadefs.h,遇到了這個。

    #define __crt_va_arg(ap, t)                                               \
        ((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \
            ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64))             \
            :  *(t* )((ap += sizeof(__int64)) - sizeof(__int64)))

這個有趣,他能用位計算判斷t是不是2^n。

 (sizeof(t) & (sizeof(t) - 1)) != 0

(2)遇到新語法了。

        template <typename _Ty>
        struct __vcrt_va_list_is_reference
        {
            enum : bool { __the_value = false };
        };

enum : bool { __the_value = false };

C++11中的新特性,稱為"enum class"或"strongly typed enum",但在這個程式碼中,它是匿名的(沒有名稱)

應該是像這樣enum : bool { a,b=false,c,d=true};。

(3)C++版本更新,

這玩意像遊戲還搞更新呢,不過聽說沒人更新(除了C++11)就是了。

CHATGPT

  1. C++98(ISO/IEC 14882:1998): 這是 C++ 的第一個標準版本,於1998年發布。它定義了 C++ 語言的基本框架,包括類、繼承、多態等基本概念。

  2. C++03(ISO/IEC 14882:2003): 這是對 C++98 的修訂,主要是一些小的修正和改進,而不是引入大規模的新功能。

  3. C++11(ISO/IEC 14882:2011): 2011年發布,是一個里程碑式的版本,引入了許多新的語言特性,包括自動型別推斷(auto)、範圍型別(range-based for)、右值引用、智慧指標、Lambda 運算式等。

  4. C++14(ISO/IEC 14882:2014): 這個版本在 C++11 的基礎上進行了一些改進,但並未引入大量的新功能。它於2014年發布。

  5. C++17(ISO/IEC 14882:2017): 這個版本引入了一些新特性,包括結構化繫結(structured bindings)、ifswitch 中的初始化、折疊運算符等。它於2017年發布。

  6. C++20(ISO/IEC 14882:2020): 這是目前為止最新的 C++ 標準版本,帶來了一系列的改進和新特性,包括概念(concepts)、協程(coroutines)、範本的類型推斷改進、<span> 標頭文件、三向比較運算符等。

未來,熟悉CHATGPT文體的人將會獲得優勢而生存下來!

(4)C++的模板重載

        template <typename _Ty>
        struct __vcrt_va_list_is_reference
        {
            enum : bool { __the_value = false };
        };

        template <typename _Ty>
        struct __vcrt_va_list_is_reference<_Ty&>
        {
            enum : bool { __the_value = true };
        };

        template <typename _Ty>
        struct __vcrt_va_list_is_reference<_Ty&&>
        {
            enum : bool { __the_value = true };
        };

        template <typename _Ty>
        struct __vcrt_assert_va_start_is_not_reference
        {
            static_assert(!__vcrt_va_list_is_reference<_Ty>::__the_value,
                "va_start argument must not have reference type and must not be parenthesized");
        };

        struct __vcrt_va_list_is_reference
        struct __vcrt_va_list_is_reference<_Ty&>
        struct __vcrt_va_list_is_reference<_Ty&&>
 

這三個重載了。

第一次看到模板結構。

        struct __vcrt_va_list_is_reference<_Ty&&>

這裡的<_Ty&&>用了一個叫右值引用的東西。

事情逐漸變得陰間,這背後需要學點其他東西。

我列個列表:

模板結構、模板結構重載、左值引用、右值引用、模板引數清單、模板特例化

一個一個來。

模板引數清單、模板特例化

// 通用模板

template <typename T>

struct MyTemplate

{

        void print() { std::cout << "Generic Template\n";

       }

};

// 模板特例化:針對 int 型別提供特定的實作

template <>

struct MyTemplate<int>

{

        void print()

        {

                std::cout << "Specialized Template for int\n";

        }

};

模板特例化,你用MyTemplate<5>他優先用int版本,用MyTemplate<3.14>則只能用typename T版本。

引用特例化

/*template <typename T>
struct MyTemplate<int>
{
    void print()
    {
        std::cout << "Specialized Template for int\n";
    }
};上方ERROR,下方CORRECT*/

template <typename T>
struct MyTemplate<T&>
{
    void print() {
        std::cout << "Generic Template\n";
    }
};

這是引用特例化,<int&> 、<char&>都會跑這東西去。至此模板結構特例化(在沒學之前我叫他模板結構重載)結束。

現在是2024/01/12的台灣冬天好冷,三個小時後換第n+2天,這n必須給排前面,

第一+n天看起來真不舒服。

接下來看,左右值引用是什麼鬼東東。

出現了新的鬼東西,移動語義(Move Semantics)和 完美轉發(Perfect Forwarding)

來看左右的概念。

CHATGPT

  1. 左值(Lvalue):

    • 左值是有名字的、可識別的對象。它們有持久的身份,可以出現在等號的左邊(可以被賦值)。
    • 例如,變數、對象成員,以及引用都是左值。示例:int x = 5; 中的 x 是左值。
  2. 右值(Rvalue):

    • 右值是臨時的、一次性的值。它們通常出現在等號的右邊,不能被取址。
    • 例如,常數、計算結果、臨時對象,以及一些字面常量都是右值。示例:int y = 10 + 5; 中的 10 + 5 是右值。

再來看 移動語義(Move Semantics)和 完美轉發(Perfect Forwarding)

CHATGPT

移動語義(Move Semantics): 右值引用通常用於實現移動構造函數和移動賦值運算符,從而允許對象之間的資源轉移而不是複製。這在處理大型資料結構或動態資源時,可以提高效能。

完美轉發(Perfect Forwarding): 右值引用也常用於實現模板中的完美轉發,使函數可以轉發它收到的引數的值類型。

看不懂,不重要。

		std::vector<int> source = { 1, 2, 3, 4, 5 };

		// 傳統的複製,會複製整個 vector
		std::vector<int> copy = source;

		std::vector<int>& referenced = source;

		// 使用移動語義,轉移資源,避免深層複製
		std::vector<int>&& moved = std::move(source);

		std::cout << "Size of source: " << source.size() << std::endl; // 0
		std::cout << "Size of moved: " << moved.size() << std::endl;   // 5

沒引用的=運算符就是給你複製了。

&運算符就是給你一個雙子複製人,你玩referenced ,source也會變。

&&就是奪舍,moved會能操控source的東東,source就被掏空廢掉了,這個非常高效。

但是我輸出是5 5,source還是有東西。

chatgpt的問題或msvc的特性吧?不管了

回來看

        template <typename _Ty>
        struct __vcrt_va_list_is_reference<_Ty&&>
        {
            enum : bool { __the_value = true };
        };

這是一個針對右值引用的特例化,給傳入像是    A<int&&> myA2;

除此之外 n級指針 類型 左和右引用 都能特例化,c++11你真有事。

(5)來看pragma

#pragma warning(pop) // _VCRUNTIME_DISABLED_WARNINGS
#pragma pack(pop)

 pragma是來教編譯器做事的預處理器指令

#pragma pack(pop)

這要看內存對齊,告訴編譯器這樣搞。

還有pack跟warning背後的Stack。

CHATGPT

這個堆疊式管理是以編譯單元(translation unit)為範圍的,而不是全局的。換句話說,它僅在同一編譯單元內有效。如果你在不同的編譯單元中使用 #pragma warning(push)#pragma warning(pop),它們將分別維護各自的堆疊,不會相互影響。

 不同的編譯區的warning跟pack有自己的Stack

CHATGPT

在C++標準或C標準中,並沒有提供標準的方式來直接訪問或操作警告堆疊。#pragma warning(push)#pragma warning(pop) 是特定於編譯器的擴展,其實現方式和堆疊的內部結構可能取決於具體的編譯器實現。

不同的編譯器可能有不同的實現方式,而這種實現方式通常並不是公開文檔的一部分。因此,如果你想要了解和操作警告堆疊的內部結構,你可能需要查閱相應編譯器的文檔,或者可能需要查看編譯器的源代碼(如果是開源編譯器的話)。

需要注意的是,直接操作警告堆疊的內部結構可能會導致代碼的不可移植性,因為這是編譯器的實現細節。在正常的代碼中,應該遵從標準的和編譯器的建議,並使用 #pragma warning(push)#pragma warning(pop) 來管理警告的狀態。

CHATGPT

截至我知識截止日期(2022年1月),Microsoft Visual C++(MSVC)編譯器本身並不是完全開源的。Microsoft通常會釋放部分開源代碼,例如.NET Core、Visual Studio Code等,但MSVC作為編譯器的部分主體仍然是封閉源碼。

然而,Microsoft已經在GitHub上開源了一些與MSVC相關的項目,例如Microsoft/STL(標準模板庫的實現)和MicrosoftDocs/cpp-docs(MSVC的文檔)。這些項目提供了一些開放的內容,並且社區可以參與其中。

事情再繼續下去就陰間了,去翻編譯器源碼我做不到啊。

反正warning跟pack後面有Stack可以pop跟push。好用就行了。

來看內存對齊。

點名稱讚一文轻松理解内存对齐-腾讯云开发者社区-腾讯云 (tencent.com)

//2020.05.12 公众号:C语言与CPP编程
struct
{
	int i;
	char c1;
	char c2;
}Test1;

struct {
	char c1;
	int i;
	char c2;
}Test2;

struct {
	char c1;
	char c2;
	int i;
}Test3;
void funcPart7()
{


	printf("%d\n", sizeof(Test1));  // 输出8
	printf("%d\n", sizeof(Test2));  // 输出12
	printf("%d\n", sizeof(Test3));  // 输出8
}

默認是#pragma pack(4),僅僅是簡單的不同造就了大小之微小差異,在通信協定上這種頻率很致命的。

來給他改改的#pragma pack(1)

//2020.05.12 公众号:C语言与CPP编程
#pragma pack(1)
struct
{
	int i;
	char c1;
	char c2;
}Test1;

struct {
	char c1;
	int i;
	char c2;
}Test2;

struct {
	char c1;
	char c2;
	int i;
}Test3;

struct {
	char c1;
	char c2;
}Test4;
void funcPart7()
{


	printf("%d\n", sizeof(Test1));  // 输出6
	printf("%d\n", sizeof(Test2));  // 输出6
	printf("%d\n", sizeof(Test3));  // 输出6
	printf("%d\n", sizeof(Test4));  // 输出2

}

內存對齊存取快,#pragma pack(1)小。

vadefs.h結束。

來到vcruntime.h

(6)來看個warning disable

#pragma warning(disable: _VCRUNTIME_DISABLED_WARNINGS)

這是個macro大禮包,長得像下面一樣,一次ban兩個

#pragma warning(disable: 4514 4820)

CHATGPT

  1. 警告4514:

    • 含義: 未使用的內聯函數已被移除。
    • 解釋: 當編譯器檢測到某個內聯函數沒有被使用,它會發出此警告。這是編譯器提供的一種提示,但有時可能是因為某些原因(例如調試目的)而故意不使用某些內聯函數。
  2. 警告4820:

    • 含義: 結構或聯合體的佈局可能發生更改。
    • 解釋: 當編譯器檢測到在結構或聯合體的佈局中可能發生變化時,它會發出此警告。這可能是由於不同編譯器、不同編譯選項或不同版本的編譯器之間的差異而引起的。

第2+n天

阿昨天寫了一些東西不見了....補一下

(7)inline

inline就是非預處理版的小型include。都是在搞代碼插入,不過inline好像是個建議,編譯器可以不屌你。

繼續看pragma

CHATGPT

__pragma 這是 Microsoft 特定的擴展,它允許在 #pragma 中使用複雜的指令,這些指令在標準的 #pragma 中可能無法實現。__pragma 是 Microsoft Visual C++ 的擴展,可能不受其他編譯器支持。

msvc自己的#pragma關鍵字。

(8)來看這個_KERNEL_MODE

#ifndef _HAS_EXCEPTIONS // Predefine as 0 to disable exceptions
    #ifdef _KERNEL_MODE
        #define _HAS_EXCEPTIONS 0
    #else
        #define _HAS_EXCEPTIONS 1
    #endif /* _KERNEL_MODE */
#endif /* _HAS_EXCEPTIONS */

這是個Preprocessor Macros,M爹有文檔可看/kernel (Create kernel mode binary) | Microsoft Learn

來看M爹的代碼片段、很簡單

#ifdef _KERNEL_MODE

#define NONPAGESECTION __declspec(code_seg("$kerneltext$"))

#else

#define NONPAGESECTION

#endif

class NONPAGESECTION MyNonPagedClass { // ... };

事情有點複雜

列個表

RTTI、the exception specification throw()、/kernel、/GS Build Option、__forceinline Qualifier、__declspec(code_seg("$kerneltext$"))、用戶空間、內核空間

先說開啟/kernel

CHATGPT

  1. 進入屬性設置: 在專案屬性視窗中,找到“配置屬性”一節,點擊“C/C++” 類別。

  2. 設置 /kernel 選項: 在“C/C++”類別中,找到“命令行”選項。在“附加選項”中添加 /kernel

  3. 保存設置: 確保保存你的設置。

嘗試寫個

    try {
        
    }
    catch (...) {
    }

應該會報錯,

因為vcruntime.h直接或間接的include在你的代碼中,而他有這段代碼

#ifndef _HAS_EXCEPTIONS // Predefine as 0 to disable exceptions
    #ifdef _KERNEL_MODE
        #define _HAS_EXCEPTIONS 0
    #else
        #define _HAS_EXCEPTIONS 1
    #endif /* _KERNEL_MODE */
#endif /* _HAS_EXCEPTIONS */

把例外給幹掉了。

CHATGPT

  • C++ 異常處理被禁用。使用 throwtry 關鍵字會導致編譯錯誤,除非使用異常規格 throw()/kernel 不與 /EH 選項兼容,除非使用 /EH-
  • 動態類型信息(RTTI)被禁用。使用 dynamic_casttypeid 關鍵字會導致編譯錯誤,除非靜態使用 dynamic_cast
  • 對於 newdelete 運算符,需要明確定義。編譯器和運行時不提供默認定義。

還幹掉了一些其他功能,不過禁用的原碼沒找著。

又深又複雜的計算機深淵。

來看用戶空間、內核空間

CHATGPT

使用者空間和內核空間是作業系統中的兩個不同的虛擬地址空間。使用者空間包含應用程式的代碼和數據,而內核空間包含作業系統的核心組件和驅動程式。通常,普通的應用程式代碼在使用者空間中運行,而需要特殊權限的作業系統代碼和驅動程式在內核空間中運行。

剩下RTTI、the exception specification throw()/GS Build Option、__forceinline Qualifier、__declspec(code_seg("$kerneltext$"))、

(9)"GuardStack"

來開他。預設是開啟的。

CHATGPT

在 Visual Studio 中的项目属性中的 "C/C++ -> Code Generation -> Buffer Security Check" 选项可以用于启用或禁用 /GS

/GS 是 Microsoft Visual C++ 编译器的一项安全特性,用于提高程序的安全性。具体来说,/GS 是用于缓解栈溢出攻击的一种技术,这种攻击通常是通过操纵程序的栈来执行恶意代码的一种手段。

具体来说,/GS 实施了一些机制,包括:

  1. 栈保护检查(Stack Protector): 在函数调用时,在栈上添加一个特殊的标记值。在函数返回前,检查这个标记值是否被篡改。如果发现栈被溢出,程序将被终止。

  2. 安全 Cookie: 在栈上插入一个随机生成的值,也称为安全 cookie 或 GS cookie。这个值被用于检测缓冲区溢出攻击。如果攻击者试图修改栈上的数据,就会破坏安全 cookie。

GS好,開好他。

(10)RTTI

CHATGPT

RTTI 是 "Run-Time Type Information"(运行时类型信息)的缩写。它是一种 C++ 的特性,提供在运行时查询对象类型信息的机制。RTTI 允许你在程序运行时获取对象的实际类型,而不仅仅是在编译时。

上資源

Type support (basic types, RTTI) - cppreference.com

Fundamental types - cppreference.com

Type - cppreference.com

Fixed width integer types (since C++11) - cppreference.com

Fixed width floating-point types (since C++23) - cppreference.com

發現個神奇的東西

std::float128_t :D 128位的float,在stdfloat裡面

#if !_HAS_CXX23
_EMIT_STL_WARNING(STL4038, "The contents of <stdfloat> are available only with C++23 or later.");
#else // ^^^ !_HAS_CXX23 / _HAS_CXX23 vvv

結果是個c++23的東西,用不了。

已開始重建...
1>------ 已開始全部重建: 專案: testing_ground, 設定: Debug x64 ------
1>test.cpp
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\include\stdfloat(13): warning STL4038: The contents of <stdfloat> are available only with C++23 or later.

1>testing_ground.vcxproj -> D:\Source Code\Repos\iostream學習\basic_istream\x64\Debug\testing_ground.exe
1>'pwsh.exe' 不是內部或外部命令、可執行的程式或批次檔。
1>專案 "testing_ground.vcxproj" 建置完成。
==========全部重建 : 成功 1 個,失敗 0 個,略過 0 個==========
========== 重建 開始於 下午 05:48 並使用了 04.488 秒 ==========

(11)建置和編譯和偵錯的差別

CHATGPT

  1. 編譯(Compile):

    • 編譯是指将源代码文件(通常是高级语言,如C++、Java、C#等)转换为目标代码的过程。这个目标代码通常是机器码或中间代码。编译过程涉及到将源代码翻译成计算机可以执行的形式,并生成可执行文件或库文件。编译器(Compiler)是执行这个任务的工具。
  2. 建置(Build):

    • 建置是一个更广义的概念,它包括了编译以及其他一些任务,例如链接、资源管理、优化等。在软件开发中,建置通常是指将整个项目的源代码转换为可执行文件、库文件或其他构建产物的过程。它可以包括一个或多个编译阶段,以及其他构建阶段。

总的来说,编译是建置的一个子集。建置的过程可能包括多个步骤,不仅限于编译。这些步骤可能包括预处理、编译、汇编、链接、资源管理、代码优化等。在一些集成开发环境(IDE)中,"Build" 操作通常会自动执行所有必要的步骤,确保生成最终的可执行文件或库文件。

(12)相依性、依賴項

在專案右鍵->組件相依性->專案相依性,選擇要相依的專案,這樣這個專案會在選擇的專案後建置。預設為創立專案的順序。

支線有點多vcxproj跟函數的棧跟內存跟bus跟RTTI跟the exception specification throw()跟__forceinline Qualifier、__declspec(code_seg("$kerneltext$")),我們不是在看msvc代碼嗎..?

先看bus

(13)計算機科學的bus

CHATGPT

在计算机科学中,"总线"(Bus)是指计算机内部或计算机系统中用于传输数据、地址和控制信号的一组电子线路。总线是计算机中各个组件之间进行通信和数据传输的重要媒介。

总线可以分为几种不同的类型,其中包括:

  1. 数据总线(Data Bus): 用于在计算机的各个部件之间传输数据。数据总线的宽度决定了一次能传输多少位的数据。

  2. 地址总线(Address Bus): 用于指定内存或I/O设备的地址。地址总线的宽度决定了计算机的寻址能力,即能够寻址的内存或设备的数量。

  3. 控制总线(Control Bus): 用于传输控制信号,例如读/写命令、中断请求等。控制总线负责协调各个组件的操作。

总线起到了计算机中信息传递的桥梁作用。例如,当中央处理器(CPU)需要从内存中读取数据时,它通过地址总线发送目标内存地址,通过数据总线传输数据,同时使用控制总线发送相应的控制信号,以确保正确的读取操作。

(14)內存

  1. 位(Bit): 存储单元最小的单位,可以表示0或1。

  2. 字节(Byte): 通常由8个位组成,是计算机内存中的基本存储单元。一个字节可以表示一个字符或8个二进制位的数据。

  3. 地址: 每个存储单元都有一个唯一的地址,用于标识其在内存中的位置。地址通常以字节为单位递增。

  4. 字(Word): 字是一个存储单元的自然存储大小,可以是8位、16位、32位或64位,具体取决于计算机体系结构。

  5. 页面(Page): 内存通常按页面的形式进行管理,一个页面包含多个字节。页面的大小可以根据操作系统和硬件的不同而变化。

  6. 内存模块: 内存模块是存储单元的集合,可以插入到计算机的内存插槽中。内存模块的容量通常以兆字节(MB)或千兆字节(GB)为单位。

  7. 内存层次结构: 计算机内存通常分为多个层次,包括高速缓存(Cache)、随机存取存储器(RAM)和永久存储器(如硬盘、固态硬盘)。不同层次的内存具有不同的访问速度、成本和容量。

  8. 地址总线和数据总线: 内存的读写操作通过地址总线传递地址,数据通过数据总线传递。地址总线决定了内存的寻址能力,数据总线决定了每次传输的数据位数。

这些组成部分一起构成了计算机系统的内存结构,提供了对程序和数据的临时存储。记忆体的组织和层次结构对计算机的性能和存储能力有重要影响。

bit就是0或1。

字節不是字,是8個bit

解析堆栈是编译器在分析源代码时使用的一个数据结构。

  1. 寄存器: 最接近CPU的存储设备,用于存储最快速、最直接被CPU访问的数据。

  2. 高速缓存: 位于寄存器和主内存之间,用于缓存频繁访问的数据,提高访问速度。

  3. 主内存(RAM): 存放运行中的程序和数据,是CPU直接访问的主要内存部分。

  4. 辅助存储器: 包括硬盘、固态硬盘等,用于长期存储数据和程序,以及在关机后保持数据。

我人不太好了,改天去看計算機組成原理吧。

刪掉內存的部分,vcxproj跟函數的棧跟RTTI跟the exception specification throw()跟__forceinline Qualifier、__declspec(code_seg("$kerneltext$"))

第3+n天

(15)vcxpro是什麼

CHATGPT

.vcxproj 文件采用 XML(eXtensible Markup Language)格式,这使得文件的结构清晰、可读性强,并且易于解析和处理。XML 是一种标记语言,被广泛用于配置文件、数据交换和信息传递等领域。

与 HTML 类似,XML 使用标签和属性的结构来组织数据。这种结构化的格式使得文件内容更容易理解,并且可以方便地通过各种工具进行处理。XML 文件的结构具有层次性,每个元素可以包含子元素,从而形成一个树形结构。

在 Visual Studio 中,.vcxproj 文件用于描述 C++ 项目的配置信息,例如编译选项、链接选项、文件组织结构等。XML 格式的文件使得开发人员可以直观地了解和编辑项目的配置,同时也为工具和脚本提供了方便的解析和处理方式。

(16)開發副檔名

CHATGPT

  1. 选择文件扩展名: 首先,选择一个符合你应用或项目的需求的文件扩展名。确保所选扩展名没有被广泛使用,以避免冲突。

  2. 定义文件格式: 确定你的文件扩展名对应的文件格式和结构。这包括文件头部信息、数据组织方式等。如果你的文件需要与特定的应用或工具交互,确保定义的文件格式能够被理解和处理。

  3. 实现文件处理逻辑: 开发能够读取和写入你定义的文件格式的软件逻辑。这可能涉及到文件的解析、数据的读写等操作。根据你的应用场景,可能需要实现相关的库或工具。

  4. 注册文件关联(可选): 如果你的文件类型与特定的应用程序关联,可以考虑在操作系统中注册文件关联。这样,用户在双击文件时,系统会启动你的应用程序来处理这种文件类型。

  5. 测试和验证: 对开发的文件处理逻辑进行充分的测试,确保它能够正常运行,且符合你定义的文件格式。

  6. 文档化: 提供相关的文档,包括文件格式的规范、如何处理文件的说明等,以便其他开发人员或用户理解和使用你的文件格式。

请注意,定义自己的文件扩展名时,应该遵循通用的文件格式设计原则,使得你的文件能够被其他工具和应用程序正确地处理。此外,了解相关的标准和规范也是一个好的实践。

反正這事能作但複雜,跳過。

東看看西看看淺淺了解一下就回去看原碼吧。

(17)函數的棧

複雜.jpg,看起來不像問一下CHATGPT可以搞定的事情。我去學個彙編吧。

【8086汇编入门】《零基础入门学习汇编语言》_哔哩哔哩_bilibili

CHATGPT

函数的栈(stack)是计算机内存中的一块区域,用于存储函数调用期间的局部变量、函数参数、返回地址和其他与函数调用相关的信息。栈的管理是由编程语言的编译器和运行时系统负责的。在函数调用时,会发生以下主要步骤:

  1. 函数调用: 当一个函数被调用时,当前函数的执行流会暂时挂起,控制权转移到被调用函数。

  2. 保存上下文: 被调用函数的上下文(例如,当前函数的局部变量、寄存器的状态、返回地址等)会被保存到栈上。这样可以确保在函数执行完毕后能够正确地返回到调用函数。

  3. 分配局部变量: 被调用函数的局部变量(包括函数参数)会在栈上分配空间。这些局部变量的生命周期与函数调用的生命周期相对应。

  4. 执行函数体: 被调用函数的代码会开始执行,对局部变量和参数进行操作。

  5. 返回: 函数执行完毕后,返回地址会从栈中取出,控制权回到调用函数。同时,被调用函数的局部变量空间会被释放。

函数的栈空间是有限的,栈采用先进先出(FILO)的原则。每次函数调用都会在栈上分配一块新的帧,这个帧包含了该函数的局部变量和其他相关信息。当函数返回时,栈上的帧会被弹出,释放相应的空间。

以下是关于函数栈的一些关键概念:

  • 栈帧(Stack Frame): 每个函数调用在栈上都会创建一个栈帧,用于存储该函数的局部变量和相关信息。

  • 栈指针(Stack Pointer): 栈指针是一个寄存器,指向当前栈帧的顶部。栈指针的移动反映了栈的状态。

  • 基指针(Base Pointer): 在一些架构中,基指针是一个寄存器,用于指向当前函数栈帧的起始位置。它通常在函数中用于访问局部变量和参数。

函数的栈管理是由编译器生成的汇编代码和运行时系统负责的,程序员通常不需要手动管理函数栈。栈的使用是一种高效的内存管理方式,特别适合处理函数调用和返回等操作。

第4+n天學習彙編

繼續學彙編。這魚好黃。

第5+n天

處理雜事去了。

第6+n天

處理雜事去了。目前第14集。add mov sub ds數據 cs指令 ip

大神的筆記汇编语言学习笔记(【汇编语言】小甲鱼零基础汇编)_glm-CSDN博客

現在是02:00am 2024/01/18 內存、bus、跟棧都看到了,留個爪爪。

第7+n天

處理雜事去了。到第21集,21/77。

第8+n天

處理雜事去了。

第9+n天

沒雜事但是去玩電動了。

第10+n天

win10要debug去自己下載dosbox 和masm32,在window shell編譯asm文件用masm32,記得改環境變量來絲滑調用ml.exe

ml /c /coff /Cp sample.asm

link /subsystem:windows sample.obj

影片中的start要改成_start,編譯錯誤會提示你。

masm32 連結器不創建可執行檔 - 堆棧溢出 (stackoverflow.com)

每次更改asm都需要重新開dosbox 他是沙盒環境

代碼會出現在076a:01c0 用r ip 回車 01c0 回車 r 回車 u 回車 就能看到代碼了

第n+11天

到達第25集。

第n+12天

雜事+玩遊戲。

第n+13天

玩遊戲

第n+14天

出事,汇编程序设计error A2004: symbol type conflict_a2004号报错-CSDN博客,masm版本錯誤,需要masm5.0

資源在這MASM5.0下载安装与运行第一个HelloWorld_masm下载-CSDN博客,完成後繼續推進。

破除了一個迷思,dosbox不是完全隔離的沙盒,在沙盒裡面編譯後外面會出現obj。

masm5.0才是正確的,程序不會偏移到076a:01c0去,同時可以在沙盒內編譯+debug。

第n+15天

到達第35集,大約一天兩集。

第n+16,n+17,n+18,n+19天

做雜事。這彙編語言有點像是c++,有變量 也就是寄存器(會不夠用)和內存,還有迴圈,和長得像是指針的內存數組(ds:bx+si)。

37/77。

2024/02/19

以後使用日期紀錄,這樣不用每天都思考日期。

學習彙編是為了理解函數的棧,學習後確實理解了很多。

完成度(71/77),用時約37天。

在學微積分。微分的三角函數好煩,不過我找到規律了。

sin sec-<-> <-<-tan 是正的,

sec箭頭代表一個sec一個tan。

tan箭頭代表一個sec一個sec。
|   
cos csc-<-> <-<-cot 是負的

有co開頭的, cosine cosecond  cotangent d/dx後都是負的。

沒co開頭的, sine second tangent d/dx後都是正的。

sin和cos互換,cos有co開頭帶負號。

sec和tan互換,sec找自己跟tan, tan找sec兩次。

csc跟cot互換,csc找自己跟cot,cot找csc兩次,co開頭帶負號。

如此,六個三角函數的微分就搞定了。

現在來試圖解決微分問題吧!

y=(\sin((e^(\ln(x^2))^3))^2

要解決它很簡單,使用chain rule即可輕鬆破解。

y=a^2

a=sin(b)

b=c^3

c=e^d

d=ln(E)

E=x^2

接下來只要計算 dy/da *da/db *db/dc *dc/dd *dd/dE *dE/dx

d/da y

*d/db a

*d/dc b

*d/dd c

*d/dE d

*d/dx E

也就是

2a

cos(b)

3c^2

e^d

1/E

2x

很好,現在帶入a b c d E。

2a = 2sin(b)

cos(b) = cos(c^3)

3c^2 = 3(e^d)^2

e^d = e^ln(E) =E = x^2

1/E = 1/x^2

繼續帶入b c d。

2sin(b) = 2sin(c^3)

cos(c^3) = cos((e^d)^3)

3(e^d)^2 = 3(e^ln(E))^2 = 3(e^ln(x^2))^2

繼續帶入c d 。

2sin(c^3) = 2sin((e^d)^3)

cos((e^d)^3) = cos((e^(ln(E))^3) = cos((e^(ln(x^2))^3)

繼續帶入d 。

 2sin((e^d)^3) =  2sin((e^ln(E))^3) =  2sin((e^ln(x^2))^3)

然後組合起來。

y * d/dx = 2sin((e^ln(x^2))^3) * cos((e^(ln(x^2))^3) * 3(e^ln(x^2))^2 * x^2 * 1/x^2 * 2x

是不是很簡單呢,有沒有發現微分的部分都很簡單,只是帶入過程比較繁瑣而以。

彙編完結拉!彙編很有趣呢。

2024/03/02

好多天過去了,彙編已經被學習了,我還學習了鍊表,這篇文章已經和iostream沒什麼關係了,目前在學微積分,學好後把筆記也發上來。

2024/04/29成員函數指針

假設目前有一個類

class Object {
public:
    void function() const {
    }

    void function2() const {
    }

};

Object::function是 "成員函數"

&Object::function是 "成員函數的地址"

現在有一種新的數據類型 void(Object::*)()const叫做成員函數指針,它能夠被賦值"成員函數的地址"。

成員函數指針:要指定物件Object、返回值void、形參()、函數修飾詞const,如此該指針能指到準確的函數,如果有函數這四個都一樣,那這個指針就可以指到它。
 

    void (Object:: * funcPtr)() const = &Object::function;
//數據類型 void (Object3:: * )() const
//變量名稱 funcPtr
//初始化的值  &Object::function
   //另外一個返回值 物件 形參 函數修飾詞 都一樣的函數 能夠賦值給funcPtr 
 funcPtr = &Object::function2;
//要使用成員函數指針,需要一個類,和.*運算符。
//成員訪問運算符.* 
//指針到成員訪問運算符->* 
    Object obj;
    Object* objPtr;
    (obj.*funcPtr)();  // 输出: Show method from Object
    (objPtr->*funcPtr)();

由於是一種數據類型,所以可以在任何地方宣告出來,如果有物件有被初始化過的成員指針,那這個物件就會有。
 

class B {
public:
    void bFunction() const {
        std::cout << "Calling B::bFunction" << std::endl;
    }
};


class A {
public:
    // 定义一个指向 B 类成员函数的指针
    void (B::* bFuncPtr)() const = nullptr;

    // 方法,允许 A 对象调用 B 的成员函数
    void callBFunction(const B& b) const {
        if (bFuncPtr != nullptr) {
            (b.*bFuncPtr)();  // 使用成员函数指针调用 B 的成员函数
        }
    }
};

void funcPart14()
{
    A a;
    B b;
    a.bFuncPtr = &B::bFunction;
    a.callBFunction(b);
    //或者說
    (b.* (a.bFuncPtr))();
    
}

像在上方的代碼中,在A裡面宣告了一個變量,這樣A能使用B的函數。


這東西還能和模板一起用,這樣可以通用化返回值跟形參,更加強大。

class Object4 {
public:
    void display() const {
        std::cout << "Displaying Object4" << std::endl;
    }

    int count(int n) const {
        return n;
    }
};

// 模板类,用于持有任意类型成员函数的指针
template<typename ReturnType, typename... Args> //Args是數據類型
class MemberFuncPtr {
public:
    ReturnType(Object4::* funcPtr)(Args...) const;  // 成员函数指针

    // 构造函数,用于初始化成员函数指针
    MemberFuncPtr(ReturnType(Object4::* ptr)(Args...) const) : funcPtr(ptr) {}

    // 调用成员函数的方法
    ReturnType invoke(const Object4& obj, Args... args) const {
        return (obj.*funcPtr)(args...);
    }
};


void funcPart16()
{
    Object4 obj;
    MemberFuncPtr<void> displayPtr(&Object4::display);
    displayPtr.invoke(obj);  // 调用 Object4 的 display 方法

    MemberFuncPtr<int, int> countPtr(&Object4::count);
    int result = countPtr.invoke(obj, 42);  // 调用 Object4 的 count 方法,并传入参数 42
    std::cout << "Count result: " << result << std::endl;
}

->*是可以重載的,不過.*不能重載,不過未來我應該會寫一個c++ operator overloading的文章,這裡不講。

既然他是數據類型,那應該有類型轉換,不過好像非常侷限,沒什麼用。

class Object3 {
public:
    void display() const {
        std::cout << "Display method from Object3" << std::endl;
    }
    void show() const {
        std::cout << "Show method from Object3" << std::endl;
    }
};


void funcPart15()
{
    // 创建指向 Object3::display 的成员函数指针
    void (Object3:: * funcPtr)() const = &Object3::display;

    // 进行类型转换至指向 Object3::show 的成员函数指针
    funcPtr = (void(Object3::* )() const) &Object3::show;
    funcPtr = & Object3::show;

    // 使用转换后的指针调用函数
    Object3 obj;
    Object3* objPtr;
    (obj.*funcPtr)();  // 输出: Show method from Object3
    (objPtr->*funcPtr)();
    //error 不能這樣做
    //A a;
    //(a.*funcPtr)();
}

總結一下,

使用上來說,就是

返回值(物件::* 變量 )(形參) 修飾詞;

返回值(物件::* 變量 )(形參) 修飾詞 = &物件::函數。

該函數的返回值 物件 形參 修飾詞 都要和返回值(物件::* 變量 )(形參) 修飾詞一樣。(或者用模板)

(物件.*變量)(形參);或(物件指針->*變量)(形參);來調用函數

然後加上模板

ReturnValue (物件::* 變量) (Arg...)函數修飾詞;

寫了蠻多的,弄個新的文章來放好了。

2024/04/29 lambda函數的變量

觀察

class Object {
public:
	int a;
	double b;

	Object(int a, double b) : a(a), b(b) {}

	// 這個函數用來輸出成員變量a的值,將用於示範
	void display() const {
		std::cout << "Value of a: " << a << std::endl;
	}

	// 成員函數指標運算符重載
	auto operator->*(void(Object::* ptr)() const) {
		return [this, ptr]() { (this->*ptr)(); };
	}

};

void funcPart07()
{
	Object obj(10, 3.14);
	auto func = obj->* &Object::display ;  // 獲得成員函數的包裝器
	/*
	
	*/
	func();  // 呼叫函數

	void (Object:: * funcPtr2)() const = &Object::display;
	LOG(funcPtr2);/*LOG  [void (__cdecl Object::*)(void)const __ptr64] funcPtr = [1] At funcPart07     ,line 00523*/

}
CHATGPT
Lambda 类型的使用

当您使用 auto 接收这个 lambda 表达式时,您实际上是创建了一个这个匿名类的对象。这个对象可以被存储、传递和使用,就像任何其他对象一样。您可以在其他函数中使用它,或者将它作为参数传递,这提供了极高的灵活性。

先別在意那些複雜的東西,反正大概是下面那樣。

	auto func = lambda表達式
CHATGPT
为什么 lambda 函数能被变量化?

Lambda 表达式在 C++ 中本质上是一个匿名函数对象。编译器会为每个 lambda 表达式生成一个独特的类类型。这个类包含一个重载的 operator(),使得 lambda 表达式可以像普通函数一样被调用。此外,这个类还包含了 lambda 捕获的所有变量,这使得 lambda 可以存储状态。

所以,c++在背後搞鬼, lambda表達式是對象,是類,這裡只能使用auto,因為不能用什麼常見的數據類型來表達類,不是void不是void*,是一個包裝好的類func,他有捕獲的變量當成員函數,以及重載operator(),然後它可以假裝自己是個函數。像是double func(double n1){return n1}; 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值