前言
(1)该博客仅用于个人笔记
格式转换
(1)查看是
LF
行尾还是CRLF
行尾。
# 单个文件,\n 表示 LF 行尾。\r\n 表示 CRLF 行尾。
hexdump -c <yourfile>
# 单个文件,'$' 表示 LF 行尾。'^M$' 表示 CRLF 行尾。
cat -e <yourfile>
(2)将文件转换为
LF
格式。
# 单个文件
dos2unix <yourfile>
# 批量操作
find <path> -type f -exec dos2unix {} \;
(3)将文件转换为
CRLF
格式。
# 单个文件
unix2dos <yourfile>
# 批量操作
find <path> -type f -exec unix2dos {} \;
C代码规范
断言
适用场景
(1)
assert()
只能用于检测由于严重的内部逻辑错误或损坏而导致程序无法继续运行的不可恢复的错误。对于可恢复的错误,包括由于无效的外部输入而可能出现的错对于误,应返回错误值。
(2)对于返回值为esp_err_t
类型的函数,应该使用ESP_ERROR_CHECK()
而不是assert()
。
断言设置使能
(1)对于断言的设置,可以进入
menuconfig
搜索CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL
进行配置:
- Enabled : 启动断言功能。当断言失败时,会打印出断言的内容和行号。适用于开发阶段,因为它可以帮助开发者快速定位和修复代码中的错误。
- Silent : 启动静默。断言失败时,不会打印出具体的断言信息和行号,而是直接中止程序。开发者需要通过中止地址来查找断言失败的位置。适用于在某些情况下需要节省代码大小的场景,同时仍然希望保留某种程度的断言检查。
- Disabled : 禁用断言。禁用断言后,任何断言检查都不会执行,从而提高程序的性能。
“变量设置但未使用”警告
(1)如果断言被失能,那么下面的
res
可能会出现“变量设置但未使用”警告。
int res = do_something();
assert(res == 0);
(2)为了避免这样的问题,我们可以让所有的返回值用同一个变量定义,然后加上关键字即可。
int res __attribute__((unused));
res = do_something();
assert(res == 0);
res = do_something_else();
assert(res != 0);
变量
前缀
(1)静态全局变量用
g_
前缀,静态局部变量用s_
前缀。作用域仅限于当前文件的变量必须声明为静态变量static
。
static uint8_t g_num; // 静态全局变量用 g_ 前缀
int main()
{
static uint8_t s_num; // 静态局部变量用 s_ 前缀
while (1);
}
使用
(2)变量注意重入问题。 尽量在一个固定函数中操作静态全局变量。使用
get_
set_
等接口进行变量操作。
static SemaphoreHandle_t g_mutexhandle = NULL; // 变量定义要赋初值,全局静态变量以 g_ 前缀
static uint8_t g_num = 0; // 变量定义要赋初值,全局静态变量以 g_ 前缀
static void set_num(uint8_t value) // 使用 set_ 前缀接口操作变量
{
// 获得信号量
xSemaphoreTake(g_mutexhandle, portMAX_DELAY);
g_num = value;
// 释放信号量
pthread_mutex_unlock(g_mutexhandle);
}
static int get_num() // 使用 get_ 前缀接口操作变量
{
uint8_t value;
// 获得信号量
xSemaphoreTake(g_mutexhandle, portMAX_DELAY);
value = g_num;
// 释放信号量
pthread_mutex_unlock(g_mutexhandle);
return value;
}
int main()
{
static uint8_t s_count; // 静态局部变量用 s_ 前缀
// 创造互斥量
g_mutexhandle = xSemaphoreCreateMutex();
while(1){
set_num(10);
s_count = get_num(); // 静态全局变量用 g_ 前缀
vTaskDelay(pdMS_TO_TICKS(1000));
}
vSemaphoreDelete(g_mutexhandle);
g_mutexhandle = NULL; // 句柄类型变量,在对象销毁后,应重新赋值为 NULL
}
变量名
(1)变量应尽量使用有意义的词语,或者已经达成共识的符号或变量缩写
函数
(1)如果一个函数存在重入和线程安全问题,需在注释中说明。
/**
* @brief 打印函数,该函数存在线程安全问题
*
* @param __restrict 字符串
* @param ... 可变参数
* @return int
*/
int printf (const char *__restrict, ...);
(2)函数名统一使用小写,同一组件保持同一前缀。
头文件
固定格式
(1)头文件固定如下格式。
#ifndef FILE_NAME_H /* 名字要与 .c 文件对应 */
#define FILE_NAME_H
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/********************************************
* 头文件内容写里面
********************************************/
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* FILE_NAME_H */
类型定义
(1)类型都需要通过
typedef
定义并且命名
typedef enum{
MODULE_FOO_ONE,
MODULE_FOO_TWO,
MODULE_FOO_THREE
} module_foo_t; /* typedef 之后名字后缀为 _t */
typedef struct {
esp_chip_model_t model;
uint32_t features;
uint16_t revision;
uint8_t cores;
} esp_chip_info_t; /* typedef 之后名字后缀为 _t */
格式化代码
(1)如果重头写一个文件,可以使用
astyle
工具。
# 格式化代码
astyle example.c
# 使用乐鑫官方脚本
${esp-idf}/tools/format.sh <yourfile>
最终代码演示
C代码
/*********************************************************************************
* 版权声明
*********************************************************************************/
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************************************************************************
* 头文件规范
*********************************************************************************/
// #include < C 标准库头文件 >
// #include < POSIX 头文件及其常见扩展 >
// #include " IDF 头文件 "
// #include " 组件头文件,例如 FreeRTOS "
// #include " 私有头文件 "
#define uint8_t unsigned char
#define NULL 0
#define portMAX_DELAY -1
/*********************************************************************************
* 变量定义规范
*********************************************************************************/
static uint8_t g_num = 5, g_x = 0, g_y = 0; /* ","仅后面有空格,静态全局变量用 g_ 前缀 */
static uint8_t *g_z = NULL;
static SemaphoreHandle_t g_mutexhandle = NULL; // 互斥量
/*********************************************************************************
* 代码规范
*********************************************************************************/
static void circulate_function()
{/* 函数定义的括号应该单独一行 */
if (g_num) { /* 循环关键字后面加一个空格,函数内左括号与条件放在同一行 */
printf("hello");
}else if (g_x) {
printf("world");
}
for (; g_x < g_num; g_x++) { /* 循环关键字后面加一个空格,函数内左括号与循环放在同一行 */
printf("esp");
}
while (g_x){ /* 循环关键字后面加一个空格,函数内左括号与循环放在同一行 */
printf("esp32");
}
switch (g_num) { /* 循环关键字后面加一个空格,函数内左括号与循环放在同一行 */
case 0:
break;
default:
break;
}
}
/* 尽量在一个固定函数中操作静态全局变量 */
static void set_num(uint8_t value) // 使用 set_ 前缀接口操作变量
{
// 获得信号量
xSemaphoreTake(g_mutexhandle, portMAX_DELAY);
g_num = value;
// 释放信号量
pthread_mutex_unlock(g_mutexhandle);
}
static int get_num() // 使用 get_ 前缀接口操作变量
{
uint8_t value;
// 获得信号量
xSemaphoreTake(g_mutexhandle, portMAX_DELAY);
value = g_num;
// 释放信号量
pthread_mutex_unlock(g_mutexhandle);
return value;
}
int main() /* 函数之间放置一个空行 */
{
/* Tab 键为4个空格,而不是制表符进行缩进 */
/* 如果不需要这行代码,直接删除,否则就解释禁用原因。 */
// printf("hello world");
static uint8_t s_count; // 静态局部变量用 s_ 前缀
// 创造互斥量
g_mutexhandle = xSemaphoreCreateMutex();
while(1){
set_num(10);
s_count = get_num(); // 因为该变量不存在重入问题,因此不需要进行保护
vTaskDelay(pdMS_TO_TICKS(1000));
}
vSemaphoreDelete(g_mutexhandle);
g_mutexhandle = NULL; // 句柄类型变量,在对象销毁后,应重新赋值为 NULL
// 获得信号量
xSemaphoreTake(g_mutexhandle, portMAX_DELAY);
/* 一元运算符不需要空格 */
g_x = g_num++;
g_y = g_num--;
*g_z = &g_num;
g_x = !g_num;
g_x = ~g_x;
g_y = *g_z;
g_x = (uint8_t)g_x;
/* 二元运算符需要空格 */
g_num = g_x + g_y;
g_num = g_x - g_y;
g_num = g_x * g_y; /* 这个可以删除空格 */
g_num = g_x*g_y;
g_num = g_x / g_y; /* 这个可以删除空格 */
g_num = g_x/g_y;
g_num = g_x % g_y;
g_num = (g_x == g_y);
g_num = (g_x != g_y);
g_num = (g_x > g_y);
g_num = (g_x < g_y);
g_num = (g_x >= g_y);
g_num = (g_x <= g_y);
g_num = (g_x && g_y);
g_num = (g_x || g_y);
g_num = g_x & g_y;
g_num = g_x | g_y;
g_num = g_x ^ g_y;
g_num = g_x << 1;
g_num = g_x >> 1;
g_x += 3;
g_x -= 2;
g_x *= 2;
g_x /= 4;
g_x %= 2;
g_y &= 3;
g_y |= 2;
g_y ^= 3;
g_y <<= 1;
g_y >>= 1;
// 释放信号量
pthread_mutex_unlock(g_mutexhandle);
}
头文件
#ifndef FILE_NAME_H /* 名字要与 .c 文件对应 */
#define FILE_NAME_H
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* 枚举要通过 typedef 定义并且命名 */
typedef enum {
MODULE_FOO_ONE,
MODULE_FOO_TWO,
MODULE_FOO_THREE
} module_foo_t; /* typedef 之后名字后缀为 _t */
/**
* @brief 该函数存在线程安全问题
*
* @param __restrict
* @param ...
* @return int
*/
int printf (const char *__restrict, ...);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* FILE_NAME_H */
参考
(1)Espressif IoT Development Framework Style Guide