程序员的资源宝库

网站首页 > gitee 正文

C语言之不常用的预编译命令 c语言中预处理命令不是c语句

sanyeah 2024-03-29 17:37:00 gitee 7 ℃ 0 评论

C语言之不常用的预编译命令

最近在做单片机的开发,我发现一些功能需要反复使用(个人习惯),所以今天决定写一些常用的函数作为自己的库。然后发现了一个自己忽略了很久的问题,那就是怎么合理使用预编译命令来实现接口的保留。

我个人在单片机开发有个小习惯就是上电先闪一下LED灯,说明系统正常上电了,同时也是对芯片的一个简单检查吧。然后有以下两个文件:led.h和led.c

#ifndef _USER_LED_H
#define _USER_LED_H

#include "stm32f1xx_hal.h"
#include "main.h"

// 拉低点亮
#define LIGHT_UP_LOW

#ifdef LIGHT_UP_LOW
    #define GPIO_PIN_LIGHT_UP GPIO_PIN_RESET
    #define GPIO_PIN_LIGHT_DOWN GPIO_PIN_SET
#else
    #define GPIO_PIN_LIGHT_UP GPIO_PIN_SET
    #define GPIO_PIN_LIGHT_DOWN GPIO_PIN_RESET
#endif

#define LED0_LIGHTUP HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_LIGHT_UP)  // 点亮LED0
#define LED0_LIGHTDOWN HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_LIGHT_DOWN)   // 熄灭LED0
#define LED0_TOGGLE HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin)    // 切换状态

#define LED1_LIGHTUP HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_LIGHT_UP)
#define LED1_LIGHTDOWN HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_LIGHT_DOWN)
#define LED1_TOGGLE HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin)

void initLed(void);

#endif

#include "USER/LED/include/led.h"
#include "USER/tool/include/tool.h"

/**
 * @brief 
 *  板子上的三个led灯点亮一秒后关闭
 */
void initLed(void){
    LED0_LIGHTUP;
    LED1_LIGHTUP;
    SLEEP(1000);
    LED0_LIGHTDOWN;
    LED1_LIGHTDOWN;
}

在这里,用到了最基础的也是大家最常用的预编译命令:

  1. #include
  2. #define
  3. #ifndef
  4. #ifdef
  5. #else
  6. #endif

这些命令来说都是我们比较熟悉的,也足够应对大多数的情况,但我遇到了一个不好处理的情况:由于板子没有显示屏,实时数据不方便,我就日常使用串口将数据发送到Pc,通过出串口助手进行数据的检查,但是,也不是所有的板子都是有串口芯片的,有的是通过USB进行模拟,也就是stm32的usb虚拟串口来实现串口发送的功能。

由于在使用时,可能不是同样的芯片,并且可能是直接使用USART进行数据发送,也可能是USB进行模拟发送,所以我打算于预留这个接口,但时候用的时候再来实现。

于是,我在tool.h文件中,定义了函数 sendDataToPc,在源文件中进行了函数体的预留。

#ifndef _USER_TOOL_H
#define _USER_TOOL_H

#define SLEEP(n) HAL_Delay(n)

#define SEND_MSG(msg) sendDataToPc((unsigned char *)msg, strlen(msg))

void sendDataToPc(unsigned char *data, int len);
void sendDigit(char *name, long long num);

#endif

#include "USER/tool/include/tool.h"
#include <stdio.h>
#include <string.h>

#define _UNDEFINE_SEND_MSG_TO_PC 1

void sendDataToPc(unsigned char *data, int len)
{
// 如果为定义这个函数,则报错,定义后修改宏 _UNDEFINE_SEND_MSG_TO_PC 为0
#if _UNDEFINE_SEND_MSG_TO_PC
#warning "The function 'sendDataToPc' need to define"
#endif

    return;
}

void sendDigit(char *name, long long num)
{
    if (name == NULL)
    {
        return;
    }
    char msg[256] = {0};
    sprintf(msg, "Parameter\"%s\": %d / %X", name, num, num);
    SEND_MSG(msg);
}

为了不影响编译时这里的一个错误导致其他地方报了N个错误,又不希望到时候导入后忘了这里没有实现,我使用了警告。

同时呢,我设置了宏 _UNDEFINE_SEND_MSG_TO_PC 为1,在实现时就可以选择实现后修改这个宏来取消这个warning或者是注释掉这一段的代码。通过预编译命令 #if 和 #warning的组合来达到既不影响其他地方引用是编译报错,又提醒自己这里还有个函数没有现实的目的。

在代码中,我们可能不会经常用到#warnign#error,比起其他的预编译,我们使用的频率会低很多,但如果是接口设计,个人感觉在需要的位置做好处理,对于后期他人的使用和代码调试,意义重大。

但同样来说,用不好也是挺头大的。我想大家都遇到过编译后error 3个,然后修改完最后一个以为可以完事的时候,error 90个的这种情况,比如某个宏未定义,然后在几个文件中被用到就会有一大堆的error,看着都心烦。

从我个人经验来看,我们应该尽量少的去使用error,而是多使用warning,并在设计时候,注意接口的耦合性,要尽可能的解耦。比如我上述的代码中,其实串口发不出数据,并不会影响我板子控制设备。比如我的功能是控制电机正转10圈,如果我编译后电机正常工作,那么这里的warning自然可以不用理会。而如果出现了问题,我需要输出信息的时候,再去实现这个函数也是可以的。

很多人可能认为warning其实是可以不用理会的,但我觉得还是应该稍微的重视一下warning,他可能在现在没有对你的程序造成影响,但未必以后不会,我们没法保证自己的测试就测完了所有的可能性,可能在某些情况下这里的warning会是致命。

除了 #warning 和 #error,我还想说一下 #pragma这个预编译命令。

预编译命令 #pragma 是功能最多的一个预编译命令了,但很多人甚至从来没有使用过。这个命令在不同的编译器下,功能也是不一样的,比如我们想在编译时输出一个信息:

#pragma message("消息文本")

虽然我们有error和warning,但是不是这样更加合理呢?但同样,这个编译命令可能在一些编译器下就会无效。

使用VS的使用,我其实常用 #pragma once, 这个命令是说这个文件只会导入一次,优点是比使用 #ifndef + #define + #endif 要简单,缺点就是如果这个命令不生效,我们还是需要去用后者来避免头文件的多次导入。

除此之外,如果使用VS开发,有些人可能就会遇到 warning 4996,虽然是warning,但是确实不能运行,其中一个方法就是在文件中使用 #pragma warning(disable:4996) 来指定关闭一个警告,这也说明了这个命令的强大。但是,我更倾向于于直接去VS的项目属性中禁用这个警告,毕竟更加的操作简单,而且一劳永逸,同时也证明这个命令其实是有点鸡肋的。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表