C语言宏的实现作业

答案:

解析:

C语言结构体偏移量计算方法

1,使用 offsetof

offsetof 宏定义在 <stddef.h> 头文件中,是获取结构体成员偏移量的标准方法

举例:

2,手动实现offsetof

cpp 复制代码
#define MY_OFFSETOF(type, member) \
    ((size_t)&((type *)0)->member)

// 或者更安全的版本
#define MY_OFFSETOF(type, member) \
    ((size_t)((char *)&((type *)0)->member - (char *)0))

(type*)0是将数值0强制转换为指向type类型的指针,相当于创建了一个指向地址0的虚拟结构体指针。

->member,通过指针访问结构体成员,同时由于指针指向地址0,成员的地址就是它的偏移量。

解释:将一个结构体指针指向地址0时,我们实际上是在创建一个"参考坐标系",结构体的起始位置就是地址0,成员的地址 = 起始地址 + 偏移量。

数学理解:

cpp 复制代码
struct Example *ptr = some_address;
&ptr->member = (char *)ptr + offset_of_member

(char*)0就是地址0,而+ offset_of_member = 加上偏移量,结果就是偏移量的数值。

可视化理解:

cpp 复制代码
地址刻度:   0   1   2   3   4   5   ...   24   25   26   27
            [id][id][id][id][n][n]  ...   [gpa][gpa][gpa][gpa]
            ↑           ↑               ↑
            id开始      name开始        gpa开始
            
偏移量:     0           4               24
  • id 在地址 0,偏移量 = 0 - 0 = 0

  • name 在地址 4,偏移量 = 4 - 0 = 4

  • gpa 在地址 24,偏移量 = 24 - 0 = 24

&((type *)0)->member,取成员地址,其实就是成员的偏移量。

((size_t)&((type *)0)->member),最后就是直接将成员地址转换为size_t类型。

但是我们可以使用一个更加安全的版本

cpp 复制代码
((size_t)((char *)&((type *)0)->member - (char *)0))

成员地址转换为char *(字节指针),减去基地址(char*)0,结果就是字节偏移量,再转换为size_t。

(1)使用指针算术而非直接引用

(2)符合编译器标准

第一种方法的缺点

cpp 复制代码
((size_t)&((type *)0)->member)

(1)可能出现直接对NULL指针进行解引用

(2)技术上是未定义行为,某些编译器会发出警告

注意:%zu是用于打印类型变量的格式说明符。

结构体最后的字符是结构体变量,而非结构体类型,所以宏中应该传入的是struct Student。

总结:

1、先将0转换为一个结构体类型的指针,相当于某个结构体的首地址是0。此时,每一个成员的偏移量就成了相对0的偏移量,这样就不需要减去首地址了。

2、对该指针用->访问其成员,并取出地址,由于结构体起始地址为0,此时成员偏移量直接相当于对0的偏移量,所以得到的值直接就是对首地址的偏移量。

3、取出该成员的地址,强转成size_t并打印,就求出了这个偏移量

答案:

解析:

确定整数的二进制位的奇数位和偶数位

位掩码提取法

c语言如何取二进制数的某几位 | PingCode智库

其中(1U << n) - 1中1U指的是无符号整数1,<<n是将1左移n位,-1就是减去1.

举例:1U << 3 = 1000 (二进制) = 8 (十进制),(1U << 3) - 1 = 1000 - 1 = 0111 (二进制) = 7 (十进制)

打印二进制表示

cpp 复制代码
void print_binary(unsigned int num, int bits) {
    for (int i = bits - 1; i >= 0; i--) {
        printf("%d", (num >> i) & 1);  // 提取第i位的值
        if (i % 4 == 0 && i != 0) printf(" ");  // 每4位加空格分隔
    }
    printf("\n");
}

从最高位(bits-1)到最低位(0)遍历,为什么最高位是bits-1呢,可以类比数组。(num >> i) & 1:右移i位后取最低位。

答案:C

解析:

宏不存在执行速度,它是查找替换,选C。A中宏是查找替换,无法设定递归跳出条件,自然无法递归。B中宏是查找替换,都没有执行,类型更是无从谈起。D中直接说了宏的本质。所以只要知道了宏是查找替换,其他问题也就不是问题了。

答案:B

解析:

A是宏定义,C是一个比较复杂的预编译语句,但跟条件肯定扯不上关系,D是报错用的,条件编译指令包括#if、#ifdef,#ifndef,#else,#elif、#endif等。除此之外还有#if defined(xxx)的用法。故选B。

答案:D

解析:

AB说反了,尖括号是直接去库找,双引号是先从当前目录找,再去库里找。C选项头文件不能定义全局变量,否则如果有多个文件,那链接时会冲突。故选D。D也不是十全十美,在大型项目的开发中,这也并不是一个很好的编程习惯,分类放在不同的头文件并根据特点命名是更好的选择,因为这样更加方便代码的管理和维护,就目前而言,算是一个好习惯吧。

答案:A

解析:

解决同一文件重复包含头文件

cpp 复制代码
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__

// 头文件的内容(函数声明、宏定义、类型定义等)

#endif // __SOMEFILE_H__

1,#ifndef __SOMEFILE_H__,如果未定义宏,就会编译下面的代码,而第一次包含头文件时,这个宏肯定是未定义的,而第二次包含的时候,宏已经被被定义了,所以条件为假,跳过整个头文件内容

2,#define __SOMEFILE_H__,表示这个宏已经被定义了

3,#endif,结束

现代替代方案:#pragma once

cpp 复制代码
#pragma once

// 头文件内容
// 不需要#endif
相关推荐
l1t2 小时前
利用DeepSeek实现服务器客户端模式的DuckDB原型
服务器·c语言·数据库·人工智能·postgresql·协议·duckdb
再见晴天*_*3 小时前
SpringBoot 中单独一个类中运行main方法报错:找不到或无法加载主类
java·开发语言·intellij idea
l1t4 小时前
利用美团龙猫用libxml2编写XML转CSV文件C程序
xml·c语言·libxml2·解析器
lqjun08274 小时前
Qt程序单独运行报错问题
开发语言·qt
hdsoft_huge6 小时前
Java & Spring Boot常见异常全解析:原因、危害、处理与防范
java·开发语言·spring boot
风中的微尘6 小时前
39.网络流入门
开发语言·网络·c++·算法
未来之窗软件服务7 小时前
幽冥大陆(二)RDIFSDK 接口文档:布草洗涤厂高效运营的技术桥梁C#—东方仙盟
开发语言·c#·rdif·仙盟创梦ide·东方仙盟
小冯记录编程7 小时前
C++指针陷阱:高效背后的致命危险
开发语言·c++·visual studio
1uther8 小时前
Unity核心概念⑨:Screen
开发语言·游戏·unity·c#·游戏引擎
C_Liu_8 小时前
C++:类和对象(下)
开发语言·c++