#pragma的常用方法講解,為什么有了條件編譯符號“DEBUG”還要來個Debugger.IsAttached


概述

我們在寫代碼時,總會遇到頭文件多次包含的情況,剛開始時我們使用宏定義進行控制,之后發現有#pragma once這樣簡單的東西,當時是很興奮,以為#pragma就這一種用法。唉~,現在想想當時還是年輕啊,不過還是年輕好啊。

1、什么是預處理

預處理是將源文件的文本作為翻譯的第一階段操作的文本處理步驟。 預處理不會分析源文本,但會為了查找宏調用而將源文本細分為標記。 主要包括了下面三個方面:

  1. 預處理指令
  2. 預處理運算符
  3. 預定義宏,這個有很多了,比如__FILE__、__LINE__和__DATA__等。

其中預處理指令包括:
在這里插入圖片描述
預處理運算符包括:
在這里插入圖片描述
這里有很多預處理指令符,今天我只整理pragma了。

2、什么是pragma

#pragma指令的作用是:用於指定計算機或操作系統特定的編譯器功能。C 和 C++ 的每個實現均支持某些對其主機或操作系統唯一的功能。 例如,某些程序必須對將數據放入的內存區域進行准確的控制或控制某些函數接收參數的方式。 在保留與 C 和 C++ 語言的總體兼容性的同時,#pragma 指令使每個編譯器均能夠提供特定於計算機和操作系統的功能。

根據定義,#pragma指令是計算機或操作系統特定的,並且通常對於每個編譯器而言都有所不同。 #pragma指令可用於條件語句以提供新的預處理器功能,或為編譯器提供實現所定義的信息。

語法是:

#pragma  token-string
__pragma( token-string )	//__pragma 關鍵字是特定於 Microsoft 編譯器的

token-string 是一系列字符,這些字符提供了特定的編譯器指令和參數(如果有)。 數字符號"#" 必須是位於包含#pragma指令行上的第一個非空白字符;空白字符可以分隔數字符號和詞“pragma”。 在 #pragma 之后,編譯轉換器可分析為預處理標記的所有文本。 #pragma 的參數受宏展開的約束,如果編譯器發現它無法識別的雜注,則它會發出警告並繼續編譯。

Microsoft C 和 C++ 編譯器識別以下指令:
在這里插入圖片描述
注:標注1的僅受C++編譯器支持。

好,下面就對一些常用的指令進行說明,有一些不常用,或者我們上層不常用的就不介紹了,因為我也不懂(沒有時間應用,看也看不懂)。

2.1 #pragma once

大家應該都知道:指定該文件在編譯源代碼文件時僅由編譯器包含(打開)一次。
使用 #pragma once 可減少生成次數,和使用預處理宏定義來避免多次包含文件的內容的效果是一樣的,但是需要鍵入的代碼少,可減少錯誤率,例如:

使用#progma once
#pragma once  
// Code placed here is included only once per translation unit 

使用宏定義方式
#ifndef HEADER_H_ 
#define HEADER_H_  
// Code placed here is included only once per translation unit  
#endif // HEADER_H_  

2.2 #pragma message(messageString)

不中斷編譯的情況下,發送一個字符串文字量到標准輸出。message編譯指示的典型運用是在編譯時顯示信息,例如:

#if _M_IX86 >= 500  		//查看定義的宏有沒有大於500,或者有時用作檢查有沒有定義宏
#pragma message("_M_IX86 >= 500")  
#endif 

messagestring 參數可以是擴展到字符串的宏,您可以通過任意組合將此類宏與字符串串聯起來,例如:

#pragma message( "Compiling " __FILE__ )   		//顯示被編譯的文件
#pragma message( "Last modified on " __TIMESTAMP__ ) 	//文件最后一次修改的日期和時間

如果在 message 雜注中使用預定義的宏,則該宏應返回字符串,否則必須將該宏的輸出轉換為字符串,例如:

#define STRING2(x) #x  
#define STRING(x) STRING2(x)  
  
#pragma message (__FILE__ "[" STRING(__LINE__) "]: test")   //注意把行號轉成了字符串

2.3、#pragma waring(…)

啟用編譯器警告消息的行為和選擇性修改,語法為:

#pragma warning( warning-specifier : warning-number-list [,warning-specifier : warning-number-list...] )

#pragma warning( push[ , n ] )    
#pragma warning( pop )

warning-specifier能夠是下列值之一:

在這里插入圖片描述
估計看這個表會頭暈,我來舉幾個例子:

#pragma warning( disable : 4507 34; once : 4385; error : 164 )   //這1行跟下面3行效果一樣

#pragma warning( disable : 4507 34 ) 	//不發出4507和34警告,即有4507和34警告時不顯示
#pragma warning( once : 4385 )  		//4385警告信息只報告一次
#pragma warning( error : 164 )  		//把164警告信息作為一個錯誤

注意:對於范圍 4700-4999 內的警告編號(與代碼生成相關聯),在編譯器遇到函數的左大括號時生效的警告狀態將對函數的其余部分生效。 在函數中使用 warning 雜注更改編號大於 4699 的警告的狀態只會在函數末尾之后生效。 以下示例演示如何正確放置 warning 雜注來禁用代碼生成警告消息然后還原該消息

#pragma warning(disable:4700)  //不發生4700警告
void Test() 		//這里之前的狀態管用,若把上面一行移動到函數里面,則照常發生警告
{  
   int x;  
   int y = x;   	//沒有C4700的警告
   #pragma warning(default:4700)   //4700的警告恢復到默認值
}  
  
int main() 
{  
   int x;  
   int y = x;   //發生C4700的警告
}  

下面來看warning推送和彈出的用法,語法為:

#pragma warning( push [ ,``n ] )    
#pragma warning( pop )

其中 n 表示警告等級(1 到 4)。

warning( push )指令存儲每個警告的當前警告狀態。
warning( push, n)指令存儲每個警告的當前狀態並將全局警告級別設置為 n。
warning( pop )指令 彈出推送到堆棧上的最后一個警告狀態。
在 push 和 pop 之間對警告狀態所做的任何更改都將被撤消。

#pragma warning( push )  
#pragma warning( disable : 4705 )  
#pragma warning( disable : 4706 )  
#pragma warning( disable : 4707 )  
// Some code  						//代碼的書寫,這里不會發出4705、4706、4707的警告
#pragma warning( pop )   			//會將每個警告(包括4705、4706、4707)的狀態還原為代碼開始的狀態

當你編寫頭文件時,你能用push和pop來保證任何用戶修改的警告狀態不會影響正常編譯你的頭文件。在頭文件開始的地方使用push,在結束地方使用pop。例如,假定你有一個不能順利在4級警告下編譯的頭文件,下面的代碼改變警告等級到3,然后在頭文件的結束時恢復到原來的警告等級。

#pragma warning( push, 3 )    
// Declarations/ definitions    		//要書寫的代碼
#pragma warning( pop )

2.4、#pragma comment(comment-type [,“commentstring”])

該指令將一個注釋記錄放入一個對象文件或可執行文件中。

comment-type 是一個預定義的標識符(如下所述,一共5個),它指定了注釋記錄的類型。 可選 commentstring 是一個字符串,它提供了某些注釋類型的附加信息。 由於 commentstring 是一個字符串,因此它遵循有關轉義字符、嵌入的引號 (") 和串聯的字符串的所有規則。

compiler
將編譯器的名稱和版本號置於對象文件中。 此注釋記錄將被鏈接器忽略。 如果為此記錄類型提供 commentstring 參數,則編譯器會生成警告。

exestr
將 commentstring 置於對象文件中。 在鏈接時,會將該字符串置於可執行文件內。 加載可執行文件時,不會將字符串加載到內存中;但是,可以使用在文件中查找可打印字符串的程序來找到它。 此注釋記錄類型的一個用途是將版本號或類似信息嵌入可執行文件中。

lib(這個最常用了)
將庫搜索記錄置於對象文件中。 此注釋類型必須帶有包含您希望鏈接器搜索的庫的名稱(和可能的路徑)的 commentstring 參數。 庫名稱遵循對象文件中的默認庫搜索記錄;鏈接器會搜索此庫,這就像在命令行上對其命名一樣,前提是未使用 /nodefaultlib 指定庫。 可以將多個庫搜索記錄置於同一個源文件中;各個記錄將以其在源文件中顯示的順序出現在對象文件中。

如果默認庫和添加的庫的順序很重要,則使用 /Zl 開關進行編譯會阻止將默認庫名稱置於對象模塊中。 然后,可使用另一個注釋指令在添加的庫的后面插入默認庫的名稱。 與這些指令一起列出的庫將以其在源代碼中的發現順序出現在對象模塊中。

#pragma  comment(lib, "comctl32.lib")
#pragma comment( lib, "mysql.lib" )

linker
將鏈接器選項置於對象文件中。 可以使用注釋類型來指定鏈接器選項,而不是將其傳遞到命令行或在開發環境中指定它。 例如,可以指定 /include 選項來強制包含符號:

#pragma comment(linker, "/include:__mySymbol") 

user
將一般注釋置於對象文件中。 commentstring 參數包含注釋文本。 此注釋記錄將被鏈接器忽略。

2.5、#pragma region … /endregion …

#pragma region是Visual C++中特有的預處理指令。它可以讓你折疊特定的代碼塊,從而使界面更加清潔,便於編輯其他代碼。折疊后的代碼塊不會影響編譯。你也可以隨時展開代碼塊以進行編輯等操作

int main()
{
...
#pragma region demo_region		//這里前面會有折疊符,折疊時提示 demo_region所寫內容
int a = 10;
int b = 20;
int c = ADD( a + b ); 
#pragma endregion demo_region		//需要跟#pragma region配套使用
....
}

折疊代碼塊的方法:如同Visual C++中折疊函數、類、命名空間,當代碼被包含在如上所述的指令之間后,#pragma region這一行的左邊會出現一個“-”號,單擊以折疊內容,同時“-”號會變成“+”號,再次單擊可以展開代碼塊

2.6、#pragma alias(…)

指定 short_filename 將用作 long_filename 的別名,語法為:

#pragma include_alias( "long_filename ", "short_filename" )  
#pragma include_alias( <long_filename>, <short_filename> ) 

某些文件系統允許長度超過 8.3 FAT 文件系統限制的頭文件名。 因為較長的頭文件名的前八個字符可能不是唯一的,因此編譯器無法簡單地將較長的名稱截斷為 8.3。 每當編譯器遇到 long_filename 字符串時,都將替換 short_filename,並改為查找頭文件 short_filename。 此雜注必須在相應的 #include 指令之前出現。

要搜索的別名必須完全符合規范,無論是大小寫、拼寫還是雙引號或尖括號的使用。 include_alias 雜注對文件名執行簡單的字符串匹配;將不執行任何其他文件名驗證。 例如:

#pragma include_alias("mymath.h", "math.h")  	//要在頭文件引用之前定義

#include "./mymath.h"  			//多了"./",必須完全匹配
#include "sys/mymath.h"  		//多了"sys/ "

您可以使用 include_alias 雜注將任何頭文件名映射到另一個頭文件名。 例如

#pragma include_alias( "api.h", "c:\version1.0\api.h" )  
#pragma include_alias( <stdio.h>, <newstdio.h> )  
#include "api.h"  
#include <stdio.h>  

不要將用雙引號括起來的文件名與用尖括號括起的文件名組合在一起。 例如,給定上述兩個 #pragma include_alias 指令,編譯器將不對下列 #include 指令執行任何替換:

#include <api.h>  
#include "stdio.h"  

2.7#pragma pack([ show ] | [ push | pop ] [, identifier ] , n)

指定結構、聯合和類成員的封裝對齊。其實就是改變編譯器的內存對齊方式。這個功能對於集合數據體使用,默認的數據的對齊方式占用內存比較大,可進行修改。

在沒有參數的情況下調用pack會將n設置為編譯器選項/zp中設置的值。如果未設置編譯器選項,windows默認為8,linux默認為4。

具體的使用方法為,其中n的取值必須是2的冪次方,即1、2、4、8、16等:

1. #pragma pack(show)     以警告信息的形式顯示當前字節對齊的值.
2. #pragma pack(n)        將當前字節對齊值設為 n .
3. #pragma pack()         將當前字節對齊值設為默認值(通常是8) .
4. #pragma pack(push)     將當前字節對齊值壓入編譯棧棧頂.
5. #pragma pack(pop)      將編譯棧棧頂的字節對齊值彈出並設為當前值.
6. #pragma pack(push, n)  先將當前字節對齊值壓入編譯棧棧頂, 然后再將 n 設為當前值.
7. #pragma pack(pop, n)   將編譯棧棧頂的字節對齊值彈出, 然后丟棄, 再將 n 設為當前值.

8. #pragma pack(push, identifier)        將當前字節對齊值壓入編譯棧棧頂, 然后將棧中保存該值的位置標識為 identifier .
10. #pragma pack(pop, identifier)        將編譯棧棧中標識為 identifier 位置的值彈出, 並將其設為當前值. 注意, 如果棧中所標識的位置之上還有值, 那會先被彈出並丟棄.
11. #pragma pack(push, identifier, n)    將當前字節對齊值壓入編譯棧棧頂, 然后將棧中保存該值的位置標識為 identifier, 再將 n 設為當前值.
12. #pragma pack(pop, identifier, n)     將編譯棧棧中標識為 identifier 位置的值彈出, 然后丟棄, 再將 n 設為當前值. 注意, 如果棧中所標識的位置之上還有值, 那會先被彈出並丟棄.
   
注意: 如果在棧中沒有找到 pop 中的標識符, 則編譯器忽略該指令, 而且不會彈出任何值.

使用時最好是成對出現的,要不容易引起錯誤,設置之后記得用完給恢復,如:

#pragma pack(n) 	//設置以n個字節為對齊長度
struct 
{
	int  ia;
 	char cb;
}
#pragma pack ()  	//彈出n個字節對齊長度,設置默認值為對齊長度

如果想給單獨一個結構體設置對齊長度還可以使用C++ 11 標准中的alignas。

但是不是設置#pragma pack(n)就按照設置的字節進行對齊呢?其實並不是這樣的,實際的對齊字節數要符合以下規則:
1、若設置了對齊長度n,實際對齊長度=Min(設置字節長度,結構體成員的最寬字節數)。若沒有設置n,實際對齊長度 = Min(結構體成員的最寬字節數,默認對齊長度)。
2、每個成員相對於首地址的偏移量(offset)都是實際對齊長度的整數倍,若不滿足編譯器進行填充。
3、數據集合的總大小為實際對齊長度的整數倍,若不是編譯器進行填充。

假設默認對齊為8時,看幾個例子:

#pragma pack(n)
struct Stu1
{
	short  	sa;	//2個字節
	char 	cb;	//1個字節
	int		ic;	//4個字節
	char 	cd;	//1個字節
}
#pragma pack()

cout << sizeof(Stu1) << endl;

若n為1:實際對齊長度為1 = Min(1,8)。這個就不用解釋了,相當於各個元素相加,總長度為8。
若n為2:實際對齊長度為2 = Min(2,8)。sa占兩個字節,不需要補齊。cb首地址偏移為2個字節,滿足規則二。ic的首地址偏移3(2+1)個字節,不能滿足規則二,填充一個字節到4。cd的首地址偏移為8個字節,滿足規則。現在相加的8+1=9個字節,不滿足規則三,填充一個字節,總長度為10;
若n為4:實際對齊長度為4 = Min(4,8)。這個就不用介紹了,默認時就用的這個。總長度為12。
若n>4:實際對齊長度為 4 = Min(設置字節長度,結構體成員的最寬字節數)。總長度為12。

2.8 其他

上面的有用過的,也有整理的實現現學的,我感覺有用處的。當然還有一些別的,但是我也沒有看懂這里就不整理了,有需要的可以查看我下面的參考資料第2條,我列幾個別的大概是干什么的。

#pragma hdrstop 停止編譯頭文件的,給我的感覺有點像“使用預編譯頭文件”
#pragma inline_depth( [0…255]) 設置內聯調用深度的

參考資料:
https://blog.csdn.net/jx_kingwei/article/details/367312
https://msdn.microsoft.com/zh-cn/library/d9x1s805.aspx

為什么有了條件編譯符號“DEBUG”還要來個Debugger.IsAttached

比如官方的示例代碼
if (Debugger.IsAttached)
{
// 出現未處理的異常;強行進入調試器
Debugger.Break();
}

這樣不更好嗎?
#if DEBUG
// 出現未處理的異常;強行進入調試器
Debugger.Break();
#endif

我到目前還沒有感受到IsAttached給我帶來了多大的好處,反而把發布版本不需要的代碼編譯進去了。
難道僅僅為了不用再在DEBUG和release之間切換?

C# Debugger.IsAttached 調試啟動瀏覽器 VS if DEBUG 啟動調試內容

  1. 程序集
    Debugger:調試類

引用集:System.Diagnostics.Debug.dll

  1. 方法使用
    1.調用系統默認的vb.net教程瀏覽器方法:
//調用系統默認的瀏覽器 System.Diagnostics.Process.Start("http://blog.csdn.net/testcs_dn"); 2. Debugger.IsAttached 調試啟動[c#教程](https://www.xin3721.com/eschool/CSharpxin3721/)瀏覽器 if (Debugger.IsAttached) { System.Diagnostics.Process.Start($"{baseAddress}/swagger"); //調試啟動瀏覽器 }  3.if DEBUG 啟動調試內容 1 2 3 #if DEBUG string baseAddress = "http://localhost:9000/"; #endif 
  1. 項目使用實例

  2. #if DEBUG VS System.Diagnostics.Debugger.IsAttached
    在Visual Studio中使用#if DEBUG 和System.Diagnostics.Debugger.IsAttached在Visual Studio中有什么區別?是否存在DEBUG設置了標志但沒有附加調試器的情況,或者在DEBUG未設置標志的情況下可以附加調試器的情況?

#if DEBUG:

① 是編譯時檢查,這意味着它所包含的代碼僅在DEBUG定義了預處理器符號的情況下才會包含在輸出程序集中;

② 確保在發布版本中根本不將代碼包含在程序集中。此外,所包含的代碼#if DEBUG始終在調試版本中運行-不僅僅是在調試器下運行時。

Debugger.IsAttached:

①是運行時檢查,因此調試代碼仍包含在程序集中,但僅在將調試器附加到進程時才執行;

② 表示無論調試還是發布版本都包含代碼。並且可以將調試器附加到發行版。

  1. 參考資料
    ① C#調用默認瀏覽器打開網頁的幾種方法:https://blog.csdn.net/testcs_dn/article/details/42246969

② #if(DEBUG)VS System.Diagnostics.Debugger.IsAttached:https://stackoverflow.com/questions/7073266/if-debug-vs-system-diagnostics-debugger-isattached

我是一個愛笑,認真記錄每一天進步的博主. 轉載請注明出處,商用請征得作者本人同意,謝謝!!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM