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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

专栏内容

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

开源贡献

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

文章目录

前言

经过前面几个专栏我们了解了数据库作为基础软件类似于操作系统几乎涉及到数据的应用都会使用我们也通过手写数据库内核开源了一款数据库名叫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;

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

可变长结构体定义

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

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

#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
如有错误或者疏漏欢迎指出互相学习。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 数据库

“【手写数据库所需C语言基础】可变结构体,结构体成员计算,类型强制转换为统一类型,数据库中使用C语言方法和技巧-CSDN博客” 的相关文章