C语言中宏的一些高级用法举例

C语言中宏的一些高级用法


文章目录

  • C语言中宏的一些高级用法
  • 1.字符串化
  • 2.标记的拼接
  • 3.宏的嵌套
    • 替换多条语句
    • 防止头文件被重复包含
    • 宏的可变参数应用
      • 方式1
      • 方式2
      • 方式3
  • 4.常用宏
  • 宏和函数的区别


1.字符串化

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/time.h>

#define trance(x,format) \
        printf(#x " =% " #format "\n",x)
        
int main() {

    char flag = '1';
    trance(flag,c);
    trance(flag,d);


    char* name = "zifuchuanhua";
    trance(name,s);
    trance(name,x);
     return 0;
}

结果
在这里插入图片描述

2.标记的拼接

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/time.h>

#define trance(x,format) \
        printf(#x " =% " #format "\n",x)
#define trance2(i) trance(salary ## i,d)
int main() {

    int salary1 = 10000,salary2=1209,salary3=12345;
    trance2(1);
    trance2(2);
    trance2(3);
     return 0;
}

结果
在这里插入图片描述

3.宏的嵌套

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/time.h>

#define F(f) f(args)
#define args a,b


void test(int number1,int number2){

    printf("%d + %d = %d\n",number1,number2,number1 + number2);
} 
int main() {
    int a=5;
    int b=9;
    F(test);
     return 0;
}

结果
在这里插入图片描述

替换多条语句

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/time.h>

#define STUDY( name) do{    \
    printf("study %s\n",name); \
    printf("work %s\n",name); \
    printf("learn %s\n",name); \
    printf("diy %s\n",name); \
}while(0);



int main() {
char name[] = "qrs";
    STUDY(name);
     return 0;
}

在这里插入图片描述

防止头文件被重复包含

#ifndef xxxxx
#define xxxxx

#endif

宏的可变参数应用

方式1

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/time.h>
         
#define DEBUG(fmt, ...) \
             printf("[FILE: %s] [FUNCTION: %s] [LINE: %d] " fmt "\n", \
                    __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)

int main() {
         int x = 100;
         int y = 200;
         DEBUG("x = %d, y = %d", x, y);
         return 0;
}

在这里插入图片描述

方式2

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/time.h>

#define DEBUG_ON 1
#if DEBUG_ON
#define DEBUG(fmt, ...) \
             printf("[FILE: %s] [FUNCTION: %s] [LINE: %d] " fmt "\n", \
                    __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define DEBUG(fmt,...)
#endif
int main() {
         int x = 100;
         int y = 200;
         DEBUG("x = %d, y = %d", x, y);
         return 0;
}

DEBUG_ON = 1
在这里插入图片描述

DEBUG_ON =0
在这里插入图片描述

方式3

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/time.h>

//使用这一行或注释这一行
#define DEBUG_ON 

#if DEBUG_ON
#define DEBUG(fmt, ...) \
             printf("[FILE: %s] [FUNCTION: %s] [LINE: %d] " fmt "\n", \
                    __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define DEBUG(fmt,...)
#endif
int main() {
         int x = 100;
         int y = 200;
         DEBUG("x = %d, y = %d", x, y);
         return 0;
}

4.常用宏

#define MEM_B(x) (*((byte *)(x)))
  • 一个field在结构体(struct)中的偏移量

#define FPOS(type,field)  ((dword) & ((type *)0)->field)
  • 得到一个结构体中field所占用的字节数
#define FSIZ(type,field) sizeof(((type*)O)->field)
  • 得到一个字的高位和低位字节
#define WORD_LO(xxx) ((byte)((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte)((word)(xxx) >> 8))
  • 将一个字母转换为大写
#define UPCASE(c) (((c) >= 'a' && (c) <= 'z') ? ((c) - 0X20):(c))
  • 返回数组元素的个数

#define ARR_SlZE(a) (sizeof((a))/sizeof((a[0])))
  • container_of
    此宏在内核代码 kernel/include/linux/kernel.h 中定义
#define container_of(ptr, type, member) \
    (type *)((char *)(ptr) - (char *) &((type *)0)->member)

ptr 是指向结构体成员的指针;
type 是结构体类型名;
member 是结构体成员名。
该宏定义包含一个单独的表达式,它执行以下操作:

  1. (type *)0:首先将整数 0 强制转换为指向 type 类型的指针,即创建一个空的 type 类型的指针,这样就能够在后续计算中使用结构体成员的偏移量。
  2. &((type *)0)->member:使用成员运算符 -> 访问结构体指针的成员 member,然后取其地址 &,即得到 member 在结构体中的偏移量。
  3. (char *):将偏移量强制转换为指向 char 类型的指针,以便在后续计算中以字节为单位进行偏移。
  4. (char *)(ptr):将传入的结构体成员指针 ptr 强制转换为指向 char 类型的指针,以便在后续计算中以字节为单位进行偏移。
  5. (char *)(ptr) - (char *) &((type *)0)->member:计算从结构体成员指针 ptr 到结构体指针的偏移量,即结构体成员 member 在结构体中的偏移量,然后用结构体指针的地址减去该偏移量,得到整个结构体的指针。
  6. (type *)((char *)(ptr) - (char *) &((type *)0)->member):将计算出的指针强制转换为指向 type 类型的指针,即返回整个结构体的指针。
    因此,这个宏定义的作用是通过一个结构体成员的指针,返回整个结构体的指针。它的实现原理是利用了 C 语言中结构体的内存布局特点,即结构体的第一个成员的地址就是结构体本身的地址,后续成员的地址依次递增。这样,通过结构体成员的偏移量,就能计算出整个结构体的地址。
#define container_of(ptr, type, member) ({            \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

功能:根据结构体变量成员施址获取整个结构体的存储空间首地址
参数:
ptr:结构体变量的成员地址
type:结构体类型
member:结构体成员

  • #define 是 C 语言中的宏定义关键字,它定义了一个名为 container_of 的宏,宏参数有三个,分别是 ptr、type 和 member。
  • {} 是 C 语言中的代码块,宏定义的代码块中包含了两个语句。
  • typeof 是 GCC 编译器的一个扩展,它可以获取一个表达式的类型。
  • ((type *)0)->member 是一个结构体成员访问表达式,它的意思是访问 type 结构体中的 member 成员,并返回该成员的类型。
    __mptr 是一个指向 member 成员的指针,它指向的类型是 const typeof(((type *)0)->member) *,也就是 type 结构体中 member 成员的类型的常量指针。
  • (ptr) 是一个宏参数,它表示传入的结构体成员指针。
  • (char *)__mptr 将指向 member 成员的指针转换为 char 类型的指针,这样可以通过指针运算来计算整个结构体的指针。
  • offsetof 是 C 语言标准库中的一个宏,它可以计算一个结构体中某个成员相对于结构体起始地址的偏移量。
  • (type *)((char *)__mptr - offsetof(type, member)) 是整个宏的返回值,它的意思是从 member 成员的指针计算出整个结构体的地址,并将其转换为 type * 类型的指针。
    综上所述,这个宏定义实现了一个通用的容器类型转换技巧,可以在任何包含了指定成员的结构体中使用
    而 offsetof 定义在 kernel/include/linux/stddef.h ,如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

举个例子,来简单分析一下 container_of 内部实现机制。
例如:

struct test{
    int a;
    short char b;c;
};

struct test *p = (struct test *)malloc(sizeof(struct test));
test_function(&(p->b));

int test_function(short *addr_b){//获取 struct test 结构体空间的首地址
    struct addr = test *addr;
    container_of(addr_b,struct test,b);
}

展开 container_of 宏,探究内部的实现:

typeof ( ( (struct test *)0 )->b ) ; (1)
typeof ( ( (struct test *)0 )->b ) *__mptr = addr_b ; (2)
(struct test *)( (char *)__mptr - offsetof(struct test,b)) (3)

(1) 获取成员变量 b 的类型 ,这里获取的就是 short 类型。这是 GNU_C 的扩展语法。
(2) 用获取的变量类型,定义了一个指针变量 __mptr ,并且将成员变量 b 的首地址赋值给它
(3) 这里的 offsetof(struct test,b)是用来计算成员 b 在这个 struct test结构体的偏移。 __mptr是成员 b 的首地址, 现在 减去成员 b 在结构体里面的偏移值,算出来的是不是这个结构体的首地址呀 。

宏和函数的区别

(1)函数调用时,先求出实参表达式的值,然后带入形参带参数的宏只进行简单的字符替换;
(2)函数调用是在程序运行时处理,分配临时内存;而宏展开(函函数),是在编译时进行的,展开时是不分配内存,也没有返回值,也没有值传递
(3)宏的参数没有类型,只是一个符号《展开时带入到指定字符串中。
(4)使用宏次数多时,宏展开后源程序变长,函数调用不会使源程序变长;
(5)宏替换只占用编译时间,不占用运行时间而函数调用占用的是运行时间(分配内存,传递参数,执行函数体)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/8245.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

测试开发常问面试题

Postman Postman实现接口关联 步骤 通过正则表达式或则JSON提取器取值的方式&#xff0c;提取需要的参数。将参数设置为全局变量或则环境变量。在之后接口中&#xff0c;通过{{全局变量/环境变量}}代替要替换的参数值。 - JSON提取器方式 var jsonData JSON.parse(respons…

【Spring6】数据校验:Validation

10、数据校验&#xff1a;Validation 10.1、Spring Validation概述 在开发中&#xff0c;我们经常遇到参数校验的需求&#xff0c;比如用户注册的时候&#xff0c;要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式&#xff0c…

TenserRT(三)PYTORCH 转 ONNX 详解

第三章&#xff1a;PyTorch 转 ONNX 详解 — mmdeploy 0.12.0 文档 torch.onnx — PyTorch 2.0 documentation torch.onnx.export 细解 计算图导出方法 TorchScript是一种序列化和优化PyTorch模型的格式&#xff0c;将torch.nn.Module模型转换为TorchScript的torch.jit.Scr…

unicloud 模糊查询解决方案

序 1、where和aggregate的模糊搜索 2、第一种是“你好”去匹配“你好啊大家” 3、第二种是“家啊”去匹配“啊&#xff01;你家呢” 只要有1个字匹配就匹配 4、第三种是“家啊”去匹配“啊&#xff01;你家呢” 必须有“家”又有“啊”才匹配” 想看效果&#xff0c;大家可以自…

ROBOGUIDE教程:FANUC机器人摆焊焊接功能介绍与虚拟仿真操作方法

目录 摆焊功能简介 摆焊指令介绍 摆焊功能设置 摆焊条件设置 机器人摆焊示教编程 仿真运行 摆焊功能简介 使用FANCU机器人进行弧焊焊接时&#xff0c;也可以实现摆动焊接&#xff08;简称摆焊&#xff09;。 摆焊功能是在机器人弧焊焊接时&#xff0c;焊枪面对焊接方向…

面试字节,三面HR天坑,想不到自己也会阴沟里翻船....

阎王易见&#xff0c;小鬼难缠。我一直相信这个世界上好人居多&#xff0c;但是也没想到自己也会在阴沟里翻船。我感觉自己被字节跳动的HR坑了。 在这里&#xff0c;我只想告诫大家&#xff0c;offer一定要拿到自己的手里才是真的&#xff0c;口头offer都是不牢靠的&#xff0…

【CE】Mac下的CE教程Tutorial:进阶篇(第8关:多级指针)

▒ 目录 ▒&#x1f6eb; 导读开发环境1️⃣ 第8关&#xff1a;多级指针翻译操作验证其它方案&#x1f6ec; 文章小结&#x1f4d6; 参考资料&#x1f6eb; 导读 开发环境 版本号描述文章日期2023-03-操作系统MacOS Big Sur 11.5Cheat Engine7.4.3 1️⃣ 第8关&#xff1a;多…

MySQL数据库中的函数怎样使用?

函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码在MySQL中已经给我们提供了&#xff0c;我们要做的就是在合适的业务场景调用对应的函数完成对应的业务需求即可。 那么&#xff0c;函数到底在哪儿使用呢? 我们先来看两个场景&a…

【FPGA-Spirit_V2】基于FPGA的循迹小车-小精灵V2开发板

&#x1f389;欢迎来到FPGA专栏~基于FPGA的循迹小车 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大家能…

Android下载apk并安装apk(用于软件版本升级用途)

软件版本更新是每个应用必不可少的功能&#xff0c;基本实现方案是请求服务器最新的版本号与本地的版本号对比&#xff0c;有新版本则下载apk并执行安装。请求服务器版本号与本地对比很容易&#xff0c;本文就不过多讲解&#xff0c;主要讲解下载apk到安装apk的内容。 一、所需…

Socket套接字编程(实现TCP和UDP的通信)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

(链表)移除链表元素(双指针法)

文章目录前言&#xff1a;问题描述&#xff1a;解题思路&#xff08;双指针法&#xff09;&#xff1a;代码实现&#xff1a;总结&#xff1a;前言&#xff1a; 此篇是针对链表的经典练习题。 问题描述&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请…

Js:apply/call/bind、作用域/闭包、this指向(普通,箭头,JS/Vue的this)

目录1、apply/call/bind2、作用域、作用域链和闭包核心1、预处理&#xff08;解析阶段&#xff09;——JS执行“代码段”之前2、生成执行上下文环境——对代码段(全局/函数体)进行处理3、执行上下文环境小结4、多个执行上下文环境5、作用域6、作用域和执行上下文7、从【自由变量…

小米万兆路由器里的 Docker 安装 Gitea

小米万兆路由器里的 Docker 安装 Gitea准备工作创建存储查看Docker Hub镜像信息拉取 gitea 镜像和运行容器配置通过 ssh 访问(Optional)其他小米2022年12月份发布了万兆路由器&#xff0c;里面可以使用Docker。 今天尝试在小米的万兆路由器里安装Gitea。 准备工作 先将一块US…

Java企业级开发学习笔记(2.1)MyBatis实现简单查询

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/zi0wB】 文章目录零、创建数据库与表一、基于配置文件方式使用MyBatis基本使用1.1 创建Maven项目 - MyBatisDemo1.2 在pom文件里添加相应的依赖1.3 创建与用户表对应的用户实体类 - User1.4 创建用…

没有他们,人工智能只能死翘翘

我过去写过一篇文章《很多所谓伟大的贡献&#xff0c;其实都是狗屎运》&#xff0c;今天我也写写人工智能。&#xff08;1&#xff09;人才深度神经网络如果不从明斯基和罗森布拉特说起&#xff0c;那就应该可以从1965年Ivakhnenko发明前馈神经网络说起。但关键里程碑是出自Rum…

SpringBoot2核心功能 --- 原理解析

一、Profile功能 为了方便多环境适配&#xff0c;springboot简化了profile功能。 1.1、application-profile功能 默认配置文件 application.yaml&#xff1b;任何时候都会加载指定环境配置文件 application-{env}.yaml激活指定环境配置文件激活 命令行激活&#xff1a;java -…

【快乐手撕LeetCode题解系列】—— 环形链表 II

【快乐手撕LeetCode题解系列】—— 环形链表 II&#x1f60e;前言&#x1f64c;环形链表 II&#x1f64c;画图分析&#xff1a;&#x1f60d;思路分析&#xff1a;&#x1f60d;源代码分享&#xff1a;&#x1f60d;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客…

STM32与Python上位机通过USB虚拟串口通信

文章目录前言1. 查看原理图2. 新建工程3.添加代码与烧录4. python代码编写总结问题解决思路前言 在详细阅读广大网友的教程之后&#xff0c;我对STM32和Python通过USB通信的流程烂熟于心。 尝试用ST公司的NUCLEO-L476RG板子进行简单的回环通信测试&#xff0c;发现还是存在网上…

Linux·异步IO编程框架

hi&#xff0c;大家好&#xff0c;今天分享一篇Linux异步IO编程框架文章&#xff0c;对比IO复用的epoll框架&#xff0c;到底性能提高多少&#xff1f;让我们看一看。 译者序 本文组合翻译了以下两篇文章的干货部分&#xff0c;作为 io_uring 相关的入门参考&#xff1a; Ho…