為什么要替換printf
在rust
中,有個println!
的宏,類似C語言的printf
函數,能根據指定格式打印變量的內容。println!
的使用如下:
fn main() {
let v1 = 123;
let str = "hello world";
let b: bool = true;
// 輸出: v1: 123, str: hello world, b: true
println!("v1: {}, str: {}, b: {}", v1, str, b);
}
然而在C的printf函數中,對于數據的型號需要根據%d%f%s
等之類的格式化符先指定類型,使用非常不便,如果數據類型指定錯誤,則可能打印出錯誤的值甚至引起軟件崩潰。
自定義println宏的測試效果
本文則根據C語言的宏來生成一個println
的打印接口,數據的打印格式不再依賴指定的符號,而是根據數據本身的類型自動識別打印格式。測試代碼如下所示,main 中定義了C語言常見的基礎類型,打印的效果良好。話不多少,直接看測試:
#include
#include
int main(void)
{
uint8_t v_u8 = 0;
int8_t v_i8 = -1;
uint16_t v_u16 = 0x1234;
int16_t v_i16 = -1234;
uint32_t v_u32 = 0x12345678;
int32_t v_i32 = -12345678;
uint64_t v_u64 = 0x1111222233334444;
int64_t v_i64 = -1234567890;
float v_f32 = 3.1415;
double v_f64 = -99999.123;
uint8_t arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("***************************** start *********************************\n");
println(v_u8);
println(v_u8, v_i8);
println(v_u16, v_i16, 123456, -123456);
println(v_u32, v_i32, v_u64, v_i64);
println(v_f32, v_f64);
println(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
println(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7],
arr[8], arr[9]);
printf("***************************** end **********************************\n");
return 0;
}
運行結果如下:
運行效果
宏的實現源碼
#include
#include
#define IS_SAME_TYPE(T1, T2) \
__builtin_types_compatible_p(__typeof__(T1), __typeof__(T2))
#define likely(x) __builtin_expect(!!(x), 1)
#define unlinkely(x) __builtin_expect(!!(x), 1)
#define __NAME2_HELPER(a, b) a##b
#define NAME2(a, b) __NAME2_HELPER(a, b)
#define UNIQUE_NAME(prefix) NAME2(NAME2(prefix, _), __LINE__)
#define IS_ARRAY(A) !IS_SAME_TYPE((A), &(A)[0])
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#define FOREACH_ARRAY_ELEMENT(arr, item_ptr, closure) \
do \
{ \
for (size_t i = 0; i < ARRAY_SIZE(arr); i++) \
{ \
typeof(arr[0]) *item_ptr = arr + i; \
{ \
closure \
} \
} \
} while (0)
#define _STR(s) #s
#define STR(s) _STR(s)
//
#define PRINTF printf
#define IF_LIKELY(match, s, closure) \
if (IS_SAME_TYPE(match, s)) \
{ \
closure \
}
typedef enum _type_id
{
type_char = 0,
type_i8 = 1,
type_i16 = 2,
type_i32 = 3,
type_i64 = 4,
type_u8 = 5,
type_u16 = 6,
type_u32 = 7,
type_u64 = 8,
type_f32 = 9,
type_f64 = 10,
type_custom,
} type_id_t;
#define IF_LIKEY_EQ(a, b, closure) \
if (likely(a == b)) \
{ \
closure \
}
#define PRINT_FMT(fmt, ty, var) \
do \
{ \
typeof(var) _v = var; \
PRINTF("[%s: " STR(% fmt) "] ", STR(var), (ty) * (ty *)(void *)(&_v)); \
} while (0)
#define PRINT_PARSE(type_id, var) \
do \
{ \
IF_LIKEY_EQ(type_id, type_char, PRINT_FMT(c, char, var);) \
IF_LIKEY_EQ(type_id, type_u8, PRINT_FMT(u, uint8_t, var);) \
IF_LIKEY_EQ(type_id, type_u16, PRINT_FMT(u, uint16_t, var);) \
IF_LIKEY_EQ(type_id, type_u32, PRINT_FMT(u, uint32_t, var);) \
IF_LIKEY_EQ(type_id, type_u64, PRINT_FMT(lx, uint64_t, var);) \
IF_LIKEY_EQ(type_id, type_i8, PRINT_FMT(d, int8_t, var);) \
IF_LIKEY_EQ(type_id, type_i16, PRINT_FMT(d, int16_t, var);) \
IF_LIKEY_EQ(type_id, type_i32, PRINT_FMT(d, int32_t, var);) \
IF_LIKEY_EQ(type_id, type_i64, PRINT_FMT(ld, int64_t, var);) \
IF_LIKEY_EQ(type_id, type_f32, PRINT_FMT(f, float, var);) \
IF_LIKEY_EQ(type_id, type_f64, PRINT_FMT(lf, double, var);) \
} while (0)
#define TYPE_PARSE(x) _Generic((x), \
char: type_char, \
int8_t: type_i8, \
int16_t: type_i16, \
int32_t: type_i32, \
int64_t: type_i64, \
uint8_t: type_u8, \
uint16_t: type_u16, \
uint32_t: type_u32, \
uint64_t: type_u64, \
float: type_f32, \
double: type_f64, \
default: type_u32)
#define PRINT_VAR(s) \
do \
{ \
type_id_t _type = TYPE_PARSE(s); \
PRINT_PARSE(_type, s); \
} while (0)
#define PRINT_PTR(s) \
do \
{ \
PRINTF("[%s: 0x%p]", STR(s), s); \
} while (0)
#define PRINT_ARR(fmt, s) \
do \
{ \
PRINTF("[%s: ", STR(s)); \
FOREACH_ARRAY_ELEMENT(s, pch, PRINTF(STR(% fmt), *pch);); \
PRINTF("]"); \
} while (0)
#define _PRINT_1(s1) PRINT_VAR(s1)
#define _PRINT_2(s1, s2) \
do \
{ \
PRINT_VAR(s1); \
PRINT_VAR(s2); \
} while (0)
#define _PRINT_3(s1, s2, s3) \
do \
{ \
_PRINT_2(s1, s2); \
_PRINT_1(s3); \
} while (0)
#define _PRINT_4(s1, s2, s3, s4) \
do \
{ \
_PRINT_3(s1, s2, s3); \
_PRINT_1(s4); \
} while (0)
#define _PRINT_5(s1, s2, s3, s4, s5) \
do \
{ \
_PRINT_4(s1, s2, s3, s4); \
_PRINT_1(s5); \
} while (0)
#define _PRINT_6(s1, s2, s3, s4, s5, s6) \
do \
{ \
_PRINT_5(s1, s2, s3, s4, s5); \
_PRINT_1(s6); \
} while (0)
#define _PRINT_7(s1, s2, s3, s4, s5, s6, s7) \
do \
{ \
_PRINT_6(s1, s2, s3, s4, s5, s6); \
_PRINT_1(s7); \
} while (0)
#define _PRINT_8(s1, s2, s3, s4, s5, s6, s7, s8) \
do \
{ \
_PRINT_7(s1, s2, s3, s4, s5, s6, s7); \
_PRINT_1(s8); \
} while (0)
#define _PRINT_9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
do \
{ \
_PRINT_8(s1, s2, s3, s4, s5, s6, s7, s8); \
_PRINT_1(s9); \
} while (0)
#define _PRINT_10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
do \
{ \
_PRINT_9(s1, s2, s3, s4, s5, s6, s7, s8, s9); \
_PRINT_1(s10); \
} while (0)
#define _ARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, __N, ...) __N
#define VA_NUM_ARGS(...) _ARGS_IMPL(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define _PRINT_(...) NAME2(_PRINT_, VA_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)
#define PRINTLN(args...) \
_PRINT_(args); \
PRINTF("\r\n")
#define println(args...) PRINTF("|%s +%d| : ", __FILE__, __LINE__); PRINTLN(args)
int main(void)
{
uint8_t v_u8 = 0;
int8_t v_i8 = -1;
uint16_t v_u16 = 0x1234;
int16_t v_i16 = -1234;
uint32_t v_u32 = 0x12345678;
int32_t v_i32 = -12345678;
uint64_t v_u64 = 0x1111222233334444;
int64_t v_i64 = -1234567890;
float v_f32 = 3.1415;
double v_f64 = -99999.123;
uint8_t arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("***************************** start *********************************\n");
println(v_u8);
println(v_u8, v_i8);
println(v_u16, v_i16, 123456, -123456);
println(v_u32, v_i32, v_u64, v_i64);
println(v_f32, v_f64);
println(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
println(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7],
arr[8], arr[9]);
printf("***************************** end **********************************\n");
return 0;
}
讀者可以修改PRINTF來指定輸出目標位置,如文件、串口設備等。