【手写数据库所需C语言基础】可变结构体,结构体成员计算,类型强制转换为统一类型,数据库中使用C语言方法和技巧

专栏内容

  • 手写数据库toadb
    本专栏主要介绍如何从零开发,开发的步骤,以及开发过程中的涉及的原理,遇到的问题等,让大家能跟上并且可以一起开发,让每个需要的人成为参与者。
    本专栏会定期更新,对应的代码也会定期更新,每个阶段的代码会打上tag,方便阶段学习。

开源贡献

  • toadb开源库

个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

文章目录

  • 前言
  • 概述
  • 结构体定义
    • 结构体别名
    • 结构体指针
    • 结构体嵌套定义
    • 可变长结构体定义
  • 结构体大小
    • 字节大小端
    • 结构体大小
    • 结构体紧凑格式
  • 结构体地址
    • 结构体成员首地址
    • 获取成员地址
  • 结构体赋值
    • 结构体变量赋值
    • 结体体指针成员
  • 结构体类型转换
  • 总结
  • 结尾

前言

经过前面几个专栏,我们了解了数据库作为基础软件,类似于操作系统,几乎涉及到数据的应用都会使用;我们也通过手写数据库内核,开源了一款数据库,名叫toadb,它是一个轻量级的、开源的关系型数据库,它提供了基本的SQL支持和数据存储管理功能。相比于其他成熟的数据库产品,toadb更加简单和易于理解,适合初学者和数据库内核开发人员使用。通过学习和使用toadb,我们可以更好地理解数据库的基本原理,掌握数据库的核心技术,为以后的数据库设计和优化工作打下坚实的基础。

toadb是使用C语言编写,在内核开发过程中,我们发现一些初学者,对于数据库中使用C语言方法和技巧,阅读代码时需要学习。本专栏就特别将这些方法和技巧整理出来,方便初学者系统的了解和学习,以便很快能上手数据库内核的开发,不致于在开发语言层面遇到很多障碍,更多精力在数据库理论的实践。

本专栏建议为学习过C语言基础知识的读者,可以进一步深入学习,更贴进实际项目的开发应用。

概述

本文主要分享一下,C语言中最常用的数据结构常用的使用方法和技巧。C语言为了定义复杂的数据类型,引入了数据结构 struct,可以通过对基础数据类型的组合,自定义符合现实的组合类型。因为是对于多个基础数据类型的组合,所以引出了很多问题,如数据结构的大小如何计算,成员的地址是多少,字节大小端带来的影响如何消除等等。

通过以下四部分来系统的了解结构的知识:

  • 结结体定义
  • 结构体地址
  • 结构体大小
  • 结构体赋值
  • 结构体类型转换

结构体定义

如何定义出一个符合我们代码要求的结构体类型,同时在使用中可以简单明了,下面我们一起来看一下实际中如何定义。

结构体别名

在C语言中结构体的定义很简单,如下:

#define NAME_MAX_LEN  64
struct ColumnDefInfo
{char colName[NAME_MAX_LEN];int type;int options;
};

这样就定义了一个名为ColumnDefInfo的结构定,当我们定义该类型的变量时,会如下使用

struct ColumnDefInfo stColumn; 

每次都要多写struct这个单词,当写上几十上百遍时,是不是也很烦的;这就用到C语言的一个特性,给这个结构体定义一个别名,平常使用别名就可以

typedef struct ColumnDefInfo
{char colName[NAME_MAX_LEN];int type;int options;
}ColumnDefInfo;         ColumnDefInfo stColumn;  // 定义变量 

在定义结构体struct ColumnDefInfo的同时定义别名为ColumnDefInfo,这样在定义变量或引用结构体类型的地方,就可以直接使用别名即可,是不是看这简洁很多,当然为了区分结构体类型,可以加上st等前缀,统一命名。

结构体指针

C语言的实际使用中,避免不了指针类型,结构体类型的指针也是我们常用的,当函数参数需要传递结构体时,需要动态分配空间时等等,普通写法如下:

ColumnDefInfo *pstColumn = NULL; // 定义变量 

每次都会像普通类型定义指针一样,当然也没有错,因为结构体名已经是复杂类型了,如何通过类型就能区分是值还是指针类型呢? 高手一般会如下定义。

typedef struct ColumnDefInfo *PColumnDefInfo;

或者在结构体定义时,同时定义好对应的指针类型。

typedef struct ColumnDefInfo
{char colName[NAME_MAX_LEN];int type;int options;
}ColumnDefInfo, *PColumnDefInfo;PColumnDefInfo pstColumn = NULL;  // 定义变量 

这时定义结构体指针,直接使用对应的指针类型PColumnDefInfo,这样是不是又可以简洁一些,在函数入参中,看到这样的结构体名,我们立马就可以知道它是指针类型了。

结构体嵌套定义

结构体可以定义出来很复杂的类型,但是现实世界更复杂,很多事务都有层次关系,这就必须用到嵌套的结构体定义。

比如表是有行数据组成,那么表的结构体定义中,嵌套有行的结构体定义,如下:

#define FLEXIBLE_SIZE 10
typedef struct TableMetaInfo
{int tableId;int tableType;char tableName[NAME_MAX_LEN];int colNum;ColumnDefInfo column[FLEXIBLE_SIZE];
}TableMetaInfo, *PTableMetaInfo;

这次在定义时,就直接使用了上面介绍的技巧,别名,指针类型定义。我们定义了一个表的结构体TableMetaInfo,表有名字,ID等,还有行数量,以及行的数据结构定义,因为行的数量不确定,所以这里定义是一个数组。

对于嵌套结构体,在引用成员时,就有一些麻烦,如果在几层的嵌套,可以写一长串。

PTableMetaInfo stTblInfo;
int i; // 其它代码 stTblInfo->colum[i].type = 1;

这里需要注意的是,在嵌套结构体时,要注意内层成员结构体是值类型,还是指针类型,如果是值类型就要用.来引用成员,如果是指针定类的话用->引用成员,在实际使用中,我们可以看到在一条语句中两个混合使用的情况,这就是根据不同的类型进行选择。

可变长结构体定义

每一个表中的数据行,在结构体定义时,我们是不能预知的,它可以有一行,也可以有一万行,那如何定义这个数据结构呢,这就是可变长结构体定义;可变长的数据结构定义中,有一个成员来记录变长部分的大小,如行的数量colNum,而column是行数据,它的数量在每个表中都是不一样的,由动态决定大小。

使用变长结构体方法来定义,如下

#define FLEXIBLE_SIZE 
typedef struct TableMetaInfo
{int tableId;int tableType;char tableName[NAME_MAX_LEN];int colNum;ColumnDefInfo column[FLEXIBLE_SIZE];
}TableMetaInfo, *PTableMetaInfo;

其中,行数据数组 column[FLEXIBLE_SIZE] 的维度定义FLEXIBLE_SIZE 并没有给出明确的值,这里相当于可变数组的定义

int array[] = {1,2,3};

此时,TableMetaInfo结构体默认大小中,其实没有包括行的结构定义大小,我们通过程序简单输出它们的size。

printf("table size=%d, column size=%d\n", sizeof(TableMetaInfo), sizeof(ColumnDefInfo));

得到的结果如下

table size=76, column size=72

可以看到TableMetaInfo结构体默认大小只有前四个成员的大小,并不包括行数据结构的大小。那么问题来了,如何定义变量呢?

在定义变量时,我们一般动态申请内存,再通过成员数组来访问。

结构体大小

不管是动态申请内容,还是局部变量的定义,我们都需要知道结构体占多少内存空间,尤其是在多并发之间进行交互时,要尽量减少交互数据量。
下面介绍一下结构体大小,在实际应用中的那些事儿。

字节大小端

在介绍结构体大小时,我们首先要知道计算机存储我们的变量值时,并不是按照从左到右完成从高位到低位的存储,而是不同操作系统规定了自己的一个字节顺序。

在常用的X86 CPU架构中,常用的就是小端存储,即0x1234, 在内存中低位是0x34,高位是0x12,进行了反转。

这在一些结构体转为其它类型时,常常会遇到字节序问题,还有一些网络数据转为结构体数据时,明明看似没有问题,但是成员的值就是不对,这就是不同数据对应的字节序在作怪。

结构体大小

对于结构体这一复杂的自定义类型,计算机对访问内存做了一定的优化,也就是字节对齐。如下结构体,

typedef struct A 
{char a;int b;double c;
}st_A;

这个结构体st_A中只有三个成员,sizeof(st_A)算出来是16字节,符合你的预期吗? 单从代码看,只有13字节,如何多出了3字节呢? 这就是计算机内部优化的结果,成员b的地址被对齐到了四字节上,也就是成员ab的地址相差4,而不是字面上的1字节,这样就多出了3字节。

如果定义了一个结构体类型的唯一标识,而其中成员的类型不同时,将这个标识按字节进行计算hash值时,就会存在问题,因为多出来的3字节,永远不知道它的值到底是什么,那么虽然成员的值都是一样的,但是算出来的hash却有可能不同。

结构体紧凑格式

上面介绍了,计算机会对结构体采用字节对齐的优化,当然这是一种空间换时间的方式。如果我们对于空间比较敏感时,就要放弃这种默认的优化了,这就定义成紧凑格式。

typedef struct __attribute__((packed)) A 
{char a;int b;double c;
}st_A;

这样就告诉编译器,不要在成员间加多余的字节。有多种写法,也可以用 __attribute__((aligned(1)))

结构体地址

C语言中经常使用地址来访问内存,如结构体的指针,也即地址,那么对于结构体类型的变量,它会有几种地址需要我们注意了。

结构体成员首地址

想必大家会有疑问,结构体的首地址,就是结构体指针内容嘛,不是很简单吗?

没错,是的,我们举个例子来说明。

/* 10个table ,平均每个table 中有4行数据 */
PTableMetaInfo tbl = (PTableMetaInfo)malloc(sizeof(TableMetaInfo) * 10 + sizeof(ColumnDefInfo) * 40);PTableMetaInfo pstTbl = tbl;  

这里用指针pstTbl来遍历数组tbl,那么pstTbl++都会移动sizeof(TableMetaInfo)字节,这样使用是正确的吗?

前面我们介绍了变长结构体,这里的sizeof(TableMetaInfo)中,是不包括最后一个成员的长度的,所以下一个数据结构的首地址不是通过默认的偏移得到的,这里就需要计算了,根据成员colNum来计算需要偏移多少了。

#define GET_NEXT_TABLE(addr) ((addr) + sizeof(TableMetaInfo) + (addr)->colNum * sizeof(ColumnDefInfo))

GET_NEXT_TABLE这个宏定义,就是进行可变长结构体的数组偏移计算,而不是简单的通过默认运算得到。

获取成员地址

结构体成员的地址,可以通过->.引用的方式获得,当然也可以计算获得,比如ColumnDefInfo结构体中,成员type与结构体首地址相差64字节,就可以通过首地址来计算。

通过计算方式获取成员的地址时,尤其在非紧凑格式的定义的结构体时,就需要特别注意结构体成员并不一定是基础类型的字节数,要根据结构体类型字节对齐规则进行计算;对于可变长结构体,不能使用指针的默认+1移动方式,需要自己计算偏移,这在另一篇博客《C语言可变数组 嵌套的可变数组》中有详细介绍。

结构体赋值

结构体的赋值方法不同于基础类型,也有很多方式进行赋值,需要正确的使用。

结构体变量赋值

一般结构体类型的变量,我们都会清零操作,有两种方法进行初始化为零,如下示例:

struct ColumnDefInfo stColumn = {0}; memset(&stColumn, 0x00, sizeof(stColumn));
  • 在定义时,使用初始化方式进行置零,这种方式如果只写一个0,所有内容都会置零,也可以根据成员数量和类型分别写出初始化的值;
  • 使用内存操作方式,初始化为0,这种方式要能正确计算结构体的大小;

结体体指针成员

当结构体中有指针成员时,在结构体拷贝时就会存在深拷贝和浅拷贝的问题。当一处结构体直接赋值给另一个结构体变量时,它们的指针成员指向的地址是一样的,所以释放内存时需要判空,非空时才释放。

当结构体中有可变长成员时,与指针成员一样,赋值时需要特别注意,两个结构体变量内存大小是否可以容纳新值。

结构体类型转换

在数据库中,尤其执行计划,执行器处理等地方,为了方便统一使用相同的函数调用,将不同类型的结构体会强转成统一的类型,如下所示:

typedef struct Node
{NodeType type;
}Node, *PNode;typedef struct NestLoop
{NodeType    type;PNode       leftplan;PNode       rightplan;PNode       expr;         /* join expr */int         isJoin;int         mergeType;PList       targetList;   /* result columns */
}NestLoop, *PNestLoop;PNestLoop nl = NewNode(NestLoop);
PNode node = (PNode)nl;

为了达到可以相互转换,如示例所示,在结构体NestLoop的第一个成员为type, 与结构体Node的成员是一致的,这样由NestLoop强制转换为Node类型时,就只能看到成员type了。

这样类似的其它节点类型,都可以转为结构体Node,然后根据节点类型选择不同的处理调用,进行执行,这样就可以达到统计处理调用的目的。

总结

在我们进行C语言学习时,只是学习了基础的结构体使用,需要在实际使用中不断加深对它的理解,从内存部局,成员地址对齐,拷贝赋值等各方面进行探索,在数据库中,对于C语言结构体的使用方法非常丰富,在学习数据库内核过程,我们对于C语言的驾驭也会精进。

结尾

非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring事务@Transactional即便生效也不一定能回滚

通过AOP实现事务处理可以理解为,适用try...catch... 来包裹标记了Transactional注解的方法,当方法出现了异常并且满足一定条件的时候,在catch里面我们可以设置事务回滚,没有异常则直接提交事务。 这里的“一定条件”,…

map set

目录 一、关联式容器 二、键值对 三、树形结构的关联式容器 3.1 set 3.1.1 set的介绍 3.1.2 set的使用 3.2 multiset 3.2.1 multiset的介绍 3.2.2 multiset的使用 3.3 map 3.3.1 map的介绍 3.3.2 map的使用 …

【ICN综述】信息中心网络隐私安全

ICN基本原理: 信息中心网络也是需要实现在不可信环境下可靠的信息交换和身份认证 信息中心网络采用以数据内容为中心的传输方式代替现有IP 网络中以主机为中心的通信方式,淡化信息数据物理或逻辑位置的重要性,以内容标识为代表实现数据的查找…

竞赛 深度学习猫狗分类 - python opencv cnn

文章目录 0 前言1 课题背景2 使用CNN进行猫狗分类3 数据集处理4 神经网络的编写5 Tensorflow计算图的构建6 模型的训练和测试7 预测效果8 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 **基于深度学习猫狗分类 ** 该项目较为新颖&a…

leetcode经典面试150题---5.多数元素

目录 题目描述 前置知识 代码 方法一 排序法 思路 实现 复杂度 方法二 哈希表 思路 实现 题目描述 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给…

花草世界生存技能

多菌灵 杀菌常用 阿维菌素 杀虫常用 除蚜虫 吡虫啉 有毒性 内吸性(植物吸收) 苦参碱 无毒,中药提取 内吸性药 吡虫啉,噻虫嗪、啶虫脒、苦参碱 栀子花 春秋花后修剪 牡丹 秋冬种植; 洛阳产地; 肥料 …

MySQL中如何书写update避免锁表

1. 什么是MySQL锁表? MySQL锁表是指在对某个数据表进行读写操作时,为了保证数据的一致性和完整性,系统会对该数据表进行锁定,防止其他用户对该表进行操作。 2. 为什么会出现锁表? 当多个用户同时对同一个数据表进行…

Linux中的高级IO

文章目录 1.IO1.1基本介绍1.2基础io的低效性1.3如何提高IO效率1.4五种IO模型1.5非阻塞模式的设置 2.IO多路转接之Select2.1函数的基本了解2.2fd_set理解2.3完整例子代码(会在代码中进行讲解)2.4优缺点 3.多路转接之poll3.1poll函数的介绍3.2poll服务器3.…

node教程(五)接口+会话

文章目录 一.接口1.1接口是什么?1.2接口的作用1.3接口的开发与调用1.4接口的组成 一.接口 1.1接口是什么? 接口是前后端通信的桥梁 1.2接口的作用 实现前后端通信 1.3接口的开发与调用 大多数接口都是由后端工程师开发的,开发语言不限 一般情况下接口都是由…

【iOS】知乎日报前三周总结

这几天一直在进行知乎日报的仿写,仿写过程中积累了许多实用的开发经验,并对MVC有了更深的了解,特撰此篇作以总结 目录 第一周将网络请求封装在一个单例类Manager中SDWebImage库的简单使用运用时间戳处理当前时间自定义NavigationBar 第二周在…

16. 机器学习 - 决策树

Hi,你好。我是茶桁。 在上一节课讲SVM之后,再给大家将一个新的分类模型「决策树」。我们直接开始正题。 决策树 我们从一个例子开始,来看下面这张图: 假设我们的x1 ~ x4是特征,y是最终的决定,打比方说是…

gitlab修改默认nginx端口号

gitlab与nginx部署到同一台机器上,则会导致默认80端口号冲突:所以要修改默认端口号; 第一步:修改/etc/gitlab/gitlab.rb文件:vim /etc/gitlab/gitlab.rb中找到: nginx[listen_port] 8088 (这…

直流无刷电机(BLDC)六步换相驱动

直流无刷电机(BLDC)六步换相驱动 文章目录 直流无刷电机(BLDC)六步换相驱动1. 前言2. 六步换相原理3. 电角度与机械角度4. 动手实践4.1 霍尔输出表测量4.2 换向控制4.3 代码编写 5. 总结 1. 前言 直流无刷电机相对直流有刷电机具…

基于社交网络算法的无人机航迹规划-附代码

基于社交网络算法的无人机航迹规划 文章目录 基于社交网络算法的无人机航迹规划1.社交网络搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要:本文主要介绍利用社交网络算法来优化无人机航迹规划。 …

【Java 进阶篇】Java Session 原理及快速入门

大家好,欢迎来到本篇博客。今天,我们将探讨Java Web开发中一个重要而令人兴奋的概念,即Session(会话)。Session是一种在Web应用程序中跟踪用户状态和数据的机制。我们将深入了解Session的原理,并通过示例来…

鸿运主动安全云平台任意文件下载漏洞复习

简介 深圳市强鸿电子有限公司鸿运主动安全监控云平台网页存在任意文件下载漏洞,攻击者可通过此漏洞下载网站配置文件等获得登录账号密码 漏洞复现 FOFA语法:body"./open/webApi.html" 获取网站数据库配置文件 POC:/808gps/Mobile…

Mybatis和MybatisPlus使用分页插件

Mybatis分页 安装Pagehelper插件 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.2</version></dependency>分页查询 public PageBean selectB…

Winform 实现俄罗斯方块游戏(一)

第一步&#xff0c;先用GDI绘制小正方形方块&#xff0c;其它形状的用这个方块合成 如何绘制一个方块&#xff1f;先绘制两个正方形&#xff0c;如下&#xff1a; 然后四周用梯形填充&#xff0c;内部颜色用渐变&#xff0c;这样更有立体感&#xff0c;下篇介绍如何实现。

Observability:使用 OpenTelemetry 手动检测 .NET 应用程序

作者&#xff1a;David Hope 在快节奏的软件开发领域&#xff0c;尤其是在云原生领域&#xff0c;DevOps 和 SRE 团队日益成为应用程序稳定性和增长的重要合作伙伴。 DevOps 工程师不断优化软件交付&#xff0c;而 SRE 团队则充当应用程序可靠性、可扩展性和顶级性能的管理者。…

asp.net docker-compose添加kafka和redis和zookeeper

docker-compose.yml添加 redis:image: redis:alpinekafka:image: "bitnami/kafka:3.1.1"depends_on:- zookeeperzookeeper:image: "bitnami/zookeeper:3.5.10" docker-compose.override.yml添加 redis:ports:- "6379"kafka:links: - zookeepere…