征程 6|工具鏈 VP 示例中日志打印解讀
1.引言
在上一篇文章中,介紹了 VP 是什么,并以單算子 rotate 為例,介紹了 VP API 使用方法,其中有一些日志打印的代碼顯得特別高大上
LOGE_AND_RETURN_IF(src_mat.empty(), HB_UCP_INVALID_ARGUMENT,
"Read image {} failed", src_img.c_str());
由于本人是屬于對(duì) C++不那么熟悉的同學(xué),下面會(huì)從我的視角來(lái)介紹這個(gè)問(wèn)題,如果其中有錯(cuò)誤或表述不當(dāng)?shù)牡胤?,歡迎評(píng)論指正。
其實(shí)本文是為了服務(wù)于另外一篇文章:
2.log_util.h 代碼解讀
LOGE_AND_RETURN_IF 是在 log_util.h 定義的一個(gè)宏,下面來(lái)具體看下 log_util.h 中的代碼:
#ifndef VP_CODE_07_ROTATE_INCLUDE_UTIL_LOG_UTIL_H_
#define VP_CODE_07_ROTATE_INCLUDE_UTIL_LOG_UTIL_H_
#include <sstream> //C++中標(biāo)準(zhǔn)頭文件,不做解釋
#include <utility> //C++中標(biāo)準(zhǔn)頭文件,不做解釋
#include "hlog/logging.h" // 地平線提供的頭文件,并不在當(dāng)前目錄下,為什么能包含?
#define LOGI(err_msg, ...) HFLOGM_I("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGD(err_msg, ...) HFLOGM_D("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGE(err_msg, ...) HFLOGM_E("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGW(err_msg, ...) HFLOGM_W("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGE_AND_RETURN_IF(condition, code, err_msg, ...) \
do { \
if (condition) { \
LOGE(err_msg, ##__VA_ARGS__); \
return code; \
} \
} while (0)
#define LOGE_AND_RETURN_IF_NULL(ptr, code) \
LOGE_AND_RETURN_IF(ptr == nullptr, code, NULLPTR_ERR(ptr))
#endif // VP_CODE_07_ROTATE_INCLUDE_UTIL_LOG_UTIL_H_
2.1 LOGx
來(lái)看一下日志宏定義
#define LOGI(err_msg, ...) HFLOGM_I("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGD(err_msg, ...) HFLOGM_D("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGE(err_msg, ...) HFLOGM_E("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
#define LOGW(err_msg, ...) HFLOGM_W("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
這些宏封裝了 日志打印函數(shù):
LOGI(Info):信息日志
LOGD(Debug):調(diào)試日志
LOGE(Error):錯(cuò)誤日志
LOGW(Warning):警告日志
具體解釋下其中一行:
#define LOGI(err_msg, ...) HFLOGM_I("VP_TRANSFORMATION", err_msg, ##__VA_ARGS__)
LOGI(err_msg, …) 定義了一個(gè)宏:
err_msg 是第一個(gè)參數(shù)
… 表示可以接受變長(zhǎng)參數(shù)。但… 僅是一個(gè) 占位符,它不能直接用于宏的展開(kāi)。(在下面介紹個(gè)展開(kāi)的例子)
HFLOGM_I(“VP_TRANSFORMATION”, err_msg, ##VA_ARGS):
HFLOGM_I 是 hlog/logging.h 中提供的 具體日志實(shí)現(xiàn)
“VP_TRANSFORMATION” 是 日志模塊名稱,用于分類日志來(lái)源。
err_msg 就是前面的 LOGI 中的 err_msg
VA_ARGS 是一個(gè) 占位符的替代符,用于 填充 … 傳遞的實(shí)際參數(shù)。
##VA_ARGS 的作用是 在 VA_ARGS 為空時(shí)去掉前面的逗號(hào),防止編譯錯(cuò)誤。
當(dāng)調(diào)用:LOGI(“Error code: %d”, 404);
它會(huì)展開(kāi)為:HFLOGM_I(“VP_TRANSFORMATION”, “Error code: %d”, 404);
如果調(diào)用:LOGI(“Simple message”);
它會(huì)展開(kāi)為:HFLOGM_I(“VP_TRANSFORMATION”, “Simple message”);
其中 ##VA_ARGS 確保了 VA_ARGS 為空時(shí)不會(huì)出現(xiàn)額外的 ,。(在下面介紹個(gè)展開(kāi)的例子)
2.2 LOGE_AND_RETURN_IF 宏
下面來(lái)解讀 LOGE_AND_RETURN_IF 宏的寫法:
#define LOGE_AND_RETURN_IF(condition, code, err_msg, ...) \
do { \
if (condition) { \
LOGE(err_msg, ##__VA_ARGS__); \
return code; \
} \
} while (0)
這個(gè)宏 LOGE_AND_RETURN_IF 是一個(gè)常見(jiàn)的 防御式編程(Defensive Programming) 宏,如果 condition 為真,則:
記錄錯(cuò)誤日志 LOGE(err_msg, ##VA_ARGS)
返回 code
定義一個(gè) LOGE_AND_RETURN_IF 宏時(shí),do { … } while (0)起到什么作用?
do { … } while (0) 的作用是讓整個(gè)宏塊結(jié)構(gòu)化,使它在語(yǔ)法上表現(xiàn)得像一個(gè)普通的語(yǔ)句,這樣:確保 if 語(yǔ)句和 return 被視為一個(gè)整體,不會(huì)因?yàn)?if-else 結(jié)構(gòu)導(dǎo)致錯(cuò)誤。
條件滿足,觸發(fā)錯(cuò)誤日志并返回;當(dāng) … 為空時(shí),去掉前面的 ,,防止編譯錯(cuò)誤,示例(無(wú)可變參數(shù))
LOGE_AND_RETURN_IF(error, -1, "An error occurred");
展開(kāi)后
do {
if (error) {
LOGE("An error occurred");
return -1;
}
} while (0);
##VA_ARGS 確保了 沒(méi)有多余的 , 影響 LOGE 語(yǔ)法。
(帶可變參數(shù))
LOGE_AND_RETURN_IF(value < 0, -1, "Invalid value: %d", value);
展開(kāi)后
do {
if (value < 0) {
LOGE("Invalid value: %d", value);
return -1;
}
} while (0);
2.3 do { … } while (0)作用
在前面有說(shuō) do { … } while (0) 可以:確保 if 語(yǔ)句和 return 被視為一個(gè)整體,不會(huì)因?yàn)?if-else 結(jié)構(gòu)導(dǎo)致錯(cuò)誤。
為什么 do { … } while (0) 能避免 if-else 結(jié)構(gòu)導(dǎo)致的錯(cuò)誤?if-else 結(jié)構(gòu)有什么問(wèn)題?下面來(lái)看一下
do { … } while (0) 的核心作用是讓整個(gè)宏塊在語(yǔ)法上表現(xiàn)得像一個(gè)單一語(yǔ)句,從而避免 if-else 結(jié)構(gòu)中的 懸空 else(dangling else) 和 單行 if 語(yǔ)句的 bug。
2.3.1 if-else 結(jié)構(gòu)的問(wèn)題
如果 沒(méi)有 do { … } while (0),在 if-else 語(yǔ)句中 直接用 if 語(yǔ)句包裹宏時(shí),可能導(dǎo)致 else 結(jié)構(gòu)錯(cuò)誤。錯(cuò)誤示例如下:
未使用 do { … } while (0) 定義宏
#define LOGE_AND_RETURN_IF(condition, code, err_msg, ...) \
if (condition) { \
LOGE(err_msg, ##__VA_ARGS__); \
return code; \
}
代碼中調(diào)用宏
if (x < 0)
LOGE_AND_RETURN_IF(x < -10, -1, "x is too small");
else
printf("x is positive\n");
錯(cuò)誤展開(kāi):
if (x < 0)
if (x < -10) {
LOGE("x is too small");
return -1;
} // 這里的 `if` 結(jié)束
else // 這個(gè) else 可能會(huì)匹配到錯(cuò)誤的 if!
printf("x is positive\n");
此時(shí)就會(huì)引入懸空 else 的問(wèn)題。
2.3.2 懸空 else 問(wèn)題
錯(cuò)誤現(xiàn)象:
x = -5:
x is positive // 這個(gè)輸出是不應(yīng)該出現(xiàn)的!
C/C++ 規(guī)定:else 總是匹配最近的 if。
上一節(jié)中 else 錯(cuò)誤地匹配了 if (x < -10),而不是 if (x < 0),導(dǎo)致:
x = -5 時(shí),if (x < 0) 為真,但 if (x < -10) 為假,所以 LOGE_AND_RETURN_IF 不執(zhí)行。
然而 else 仍然執(zhí)行,最終 printf(“x is positive\n”); 被錯(cuò)誤地執(zhí)行!
解決方案:使用 do { … } while (0)結(jié)構(gòu)。
2.4 NULLPTR_ERR(ptr) 宏
#define NULLPTR_ERR(ptr) #ptr " is null pointer"
#ptr 是 字符串化(stringizing)運(yùn)算符,它會(huì)把 ptr 變成字符串。
示例:
printf("%s\n", NULLPTR_ERR(my_ptr));
展開(kāi)后:
printf("%s\n", "my_ptr is null pointer");
輸出:my_ptr is null pointer
2.5 LOGE_AND_RETURN_IF_NULL(ptr, code) 宏
#define LOGE_AND_RETURN_IF_NULL(ptr, code) \
LOGE_AND_RETURN_IF(ptr == nullptr, code, NULLPTR_ERR(ptr))
這個(gè)宏會(huì):
檢查 ptr 是否為 nullptr。
如果 ptr == nullptr:
記錄日志,日志信息由 NULLPTR_ERR(ptr) 生成,例如 “ptr is null pointer”。
返回 code 作為錯(cuò)誤碼。
3.總結(jié)
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。