2024年最新嵌入式 C 语言宏配置的各种技巧_嵌入式c语言中的宏(1),2024年最新苦熬一个月

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

这时候,一般来说我会把配置相关的移到头文件中,就变成了这样:

device.h

#ifndef _DEVICE_H
#define _DEVICE_H

#define DEV_ABS 1
#define DEV_CBA 2
#define DEV_LOL 3

#ifndef DEV_SELECT
#define DEV_SELECT DEV_ABS
#endif

#if (DEV_SELECT == DEV_ABS)
#define DEV_NAME    ABS
#define DEV_ID      34
#elif(DEV_SELECT == DEV_CBA)
#define DEV_NAME    CBA
#define DEV_ID      33435
#elif(DEV_SELECT == DEV_LOL)
#define DEV_NAME    LOL
#define DEV_ID      1234
#else
#error "please select current device by DEV_SELECT"
#endif

void Device_printfMsg(void);

#endif

device.c

#include "device.h"
#include <stdint.h>
#include <stdio.h>

#define _STR(s)  #s
#define MollocDefineToStr(mal)  _STR(mal)

static const char devType[] = MollocDefineToStr(DEV_NAME);
static uint32_t devID = DEV_ID;
static const char devDName[] = MollocDefineToStr(DEV_NAME) "_" MollocDefineToStr(DEV_ID) ".local";

void Device_printfMsg(void){
  printf("Device: %s\r\n" , devType);
  printf("DevID: %u\r\n" , devID);
  printf("DomainName: %s\r\n" , devDName);
}

这样,这些配置参数就对其他include了这个头文件的文件是可见的了。

至于那句

#ifndef DEV_SELECT
#define DEV_SELECT DEV_ABS
#endif

这句可有个大好处,所有你想要拥有默认参数且想要在不同工程中都可以定制的地方都可以这么写。这样,在编译器选项中定义宏,就可以用同一套源码为不同项目生成项目定制代码。

比如在VS中可以在解决方案资源管理器中的项目条目上右键->属性,打开项目的属性页,在 C/C++ ->预处理器->预处理器定义 中定义宏

图片

CodeWarrior中则是在Edit->Standard Settings里

图片

当然,有一点点问题就是这样搞没法使用像前面类枚举那种方法来给宏赋值宏,得直接赋值数字、字符串等。

接下来。what!?还要加设备,这样下去不行!一堆#if#else会搞死人的。要是我几十W个设备,难道一个.h文件就几十万行么?我得把配置信息独立出来!

建立一个随便什么名字,甚至随便什么扩展名的文件,扔进工程文件夹,就随便起个名字叫DEVINFO.txt得了。

DEVINFO.txt

// 设备配置信息模板,根据具体设备配置

// 设备名,字符串
#define DEV_NAME    DEFAULT
// 设备ID,U32
#define DEV_ID      0

然后修改device模块:

device.h

#ifndef _DEVICE_H
#define _DEVICE_H

#ifndef DEVINFO_FILENAME
#define DEVINFO_FILENAME DEVINFO.txt
#endif

void Device_printfMsg(void);

#endif

device.c

#include "device.h"
#include <stdint.h>
#include <stdio.h>

#define _STR(s)  #s
#define MollocDefineToStr(mal)  _STR(mal)

#include MollocDefineToStr(DEVINFO_FILENAME)

static const char devType[] = MollocDefineToStr(DEV_NAME);
static uint32_t devID = DEV_ID;
static const char devDName[] = MollocDefineToStr(DEV_NAME) "_" MollocDefineToStr(DEV_ID) ".local";

void Device_printfMsg(void){
  printf("Device: %s\r\n" , devType);
  printf("DevID: %u\r\n" , devID);
  printf("DomainName: %s\r\n" , devDName);
}

图片

完美,设备相关信息全部都从外面的txt文件中读出来了,而且这个文件的文件名还是由刚刚才提到的可工程定制的宏配置的方式给出的。我们可以把其他几个设备的配置信息文件都补上。

// 设备名,字符串
#define DEV_NAME    ABS
// 设备ID,U32
#define DEV_ID      34
// 设备名,字符串
#define DEV_NAME    CBA
// 设备ID,U32
#define DEV_ID      33435
// 设备名,字符串
#define DEV_NAME    LOL
// 设备ID,U32
#define DEV_ID      1234

图片

好了,这样我们只要为所有设备各建立一个TXT的信息表,然后当需要切换不同的设备时就用前述方法改一下宏配置切换不同的文件名就好了。

要明白这个方法为什么能起作用,关键是要理解这一句:

#include MollocDefineToStr(DEVINFO_FILENAME)

我们知道,经过预处理器后,这一句就会变为

#include "DEVINFO.txt"

也许你会想:这是什么鬼,还可以include txt文件?我之前见得怎么都是include .h文件呀。 这是一个大大的误区。其实include从来没规定说一定要.h文件,其实可以是任何名字的,这个预处理器指令干的事情就是把include的文件不断递归的文本展开而已。

所以其实上面这句在经过预处理器后会被直接文本替换为对应的文件的内容,一字不差的那种。可能前后会加点注释信息。

所以这种成组绑定、十分固定的配置信息就很适合用这种方式解耦到不同的配置文件中去,按需导入即可。更进一步的,应该要专门为这些配置文件建一个文件夹进行管理。

而对于那种经常会独立更改的配置呢?

一两个的话可以通过之前说的预处理器宏定义的方式来搞定,但是一个稍微有点规模的项目总会涉及到好多好多的配置参数,这个时候就不适合都写在编译器选项里了。这个时候我会专门建一个工程配置文件,比如就叫app_cfg.h,然后把整个工程中可能用到的宏配置都汇总在这里方便修改,这时之前那种可工程定制的宏写法就特别管用了:

app_cfg.h

#define DEVINFO_FILENAME  DEVINFO_CBA.txt
// 其他宏配置选项
 ...

然后,就需要用到强制包含文件这个技巧了,相当于在所有的.c文件前面都直接加一行

#include "app_cfg.h"

这是VS2012中的:

图片

这是CodeWarrior中的

图片

然后就可以很愉快的在一个文件中操控整个工程了!

那我现在又来需求了,ID是有限制的,不能超过5000。那我就这么改。在

#include MollocDefineToStr(DEVINFO_FILENAME)

下面加一句:

#if(DEV_ID > 5000)
#error "device ID shouldn't bigger than 5000"
#endif

那这样,当我们选取CBA时就没法通过编译了

图片img

还可以通过

#ifndef DEV_ID
#error "DEV_ID lost"
#endif

检查DEV_ID是否正确进行了宏定义,或如果想要组合的条件:

#if !defined(DEV_NAME) || !defined(DEV_ID)
#error "DEV_NAME or DEV_ID malloc define lost"
#endif

然后比如某个设备需要进行代码定制处理,一种方法是在代码中直接写语句进行判断当前设备的名字之类的然后执行对应特定语句。但为了节约编码出来的代码量,同时也是为了体现宏的威力,我们同样可以用预处理指令,遗憾的是,我们没法在预处理器指令中判断字符串,但是可以判断数字,正好我们有ID可以用,所以比如我们要让设备ABS多输出一行hahaha,那代码就被改成了这样

void Device_printfMsg(void){
  printf("Device: %s\r\n" , devType);
  printf("DevID: %u\r\n" , devID);
  printf("DomainName: %s\r\n" , devDName);
#if(DEV_ID == 34)
  printf("hahaha\r\n");
#endif
}

图片

记住,这些预处理指令的本质都是在替换文本,所以,只有ABS设备时才有这一行代码,对其他设备来说压根没有见到这行代码。

当然,你可以尝试用之前那个include的方法以及其他宏方法来进一步组合定制代码,这是一项创造性工作。

最后突然又想起来一个妙招。也是我最近代码里一直在用的,

我专门搞了一个DebugMsg.h,大概长这样:

#ifndef _DEBUG_MSG_H
#define _DEBUG_MSG_H
#include <stdio.h>
#ifdef _DEBUG
  #define _dbg_printf0(format)                   ((void)printf(format))
  #define _dbg_printf1(format,p1)                ((void)printf(format,p1))
  ……
#else
  #define _dbg_printf0(format)
  #define _dbg_printf1(format,p1)
  ……
#endif
#endif

这样,所有各个模块中只要引用了这个文件就可以用统一的接口输出调试信息,只要我在主配置文件中定义_DEBUG,所有调试printf就会变成真实的printf,否则就是空语句,无调试信息:

#include "DebugMsg.h"
void Device_printfMsg(void){
  _dbg_printf0("Device_printfMsg called.\r\n");
  printf("Device: %s\r\n" , devType);
  printf("DevID: %u\r\n" , devID);
  printf("DomainName: %s\r\n" , devDName);
}

那我想要使用这个接口,却又想要为我的device模块单独设一个开关怎么办呢? 整个逻辑简单来说就是,_DEBUG是主开关,其关了所有模块的调试信息都关了,然后各个模块再有各自的开关,必须和_DEBUG一起都被定义才会使这个模块有调试信息。

那我这个模块就改成了这样。

device.h

#ifndef _DEVICE_H
#define _DEVICE_H

// malloc define _DEVICE_DEBUG to enable debug message
// #define _DEVICE_DEBUG

#ifndef DEVINFO_FILENAME
#define DEVINFO_FILENAME DEVINFO.txt
#endif

void Device_printfMsg(void);

#endif

device.c

……
#ifndef _DEVICE_DEBUG
#undef _DEBUG
#endif
#include "DebugMsg.h"

void Device_printfMsg(void){
  _dbg_printf0("Device_printfMsg called.\r\n");
  printf("Device: %s\r\n" , devType);
  printf("DevID: %u\r\n" , devID);
  printf("DomainName: %s\r\n" , devDName);
}

这样,我只有同时宏定义_ DEVICE_DEBUG和_ DEBUG时_dbg_printf0才会被宏定义为printf,否则会被宏定义为空语句,也就没有调试信息了。

这是怎么回事呢? 当预处理器读到#ifndef _ DEVICE_DEBUG这句发现未宏定义_ DEVICE_DEBUG时,它会在下一句取消_ DEBUG的宏定义,这样不管我实际有没宏定义_ DEBUG,当到了#include "DebugMsg.h"并展开后,预处理器都会认为未定义_ DEBUG,所以就会把_dbg_printf0宏定义为空语句,然后就实现了这个串联的逻辑。

后记

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

际有没宏定义_ DEBUG,当到了#include "DebugMsg.h"并展开后,预处理器都会认为未定义_ DEBUG,所以就会把_dbg_printf0宏定义为空语句,然后就实现了这个串联的逻辑。

后记

[外链图片转存中…(img-sDdij6ss-1715670667824)]
[外链图片转存中…(img-PuTQwNaB-1715670667825)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值