c语言 struct强转char*不会出问题吗?

#ifndef DIRSIZ #define DIRSIZ 14 #endif struct direct { /* directory entry …
关注者
29
被浏览
33,088

10 个回答

本来看到这么长篇的散乱问题我是不想回答的。但是难得看到个贴代码特意把代码放代码块里对齐的,鉴于这个“认真性”我觉得还是回答一下吧。

首先你担心的内存对齐产生间隙的问题是担心过度了,这里你与其担心内存对齐造成的空余空间,你不如担心字节序的问题(如果不知道什么是字节序可参考这个回答: 通过一道题讲述大小端字节序 )。因为像文件系统元数据这种on-disk的数据,你只需要管好怎么写进去怎么读出来就行。哪怕中间有一些“空隙”,但是没关系,你怎么写的读出来还怎么用就行。比如你定义了这样一个结构体:

struct mytest{
    u32 flag;
    u64 ino;
    char name[256];
t.flag = 0x1122;
t.ino  = 159;    //0x9F
t.name = "testfile"

假设是你的机器是小端字节序,在内存中的排列应该是:

+--------+  0x0x7ffed2507270  t.flag
|  0x22  |
+--------+
|  0x11  |
+--------+
|    0   |
+--------+
|    0   |
+--------+
|        |
+--------+
|        |
+--------+
|        |
+--------+
|        |
+--------+  0x0x7ffed2507278   t.ino
|  0x9F  |
+--------+
|   0    |
+--------+
|   0    |
+--------+
|   0    |
+--------+
|   0    |
+--------+
|   0    |
+--------+
|   0    |
+--------+
|   0    |
+--------+  0x0x7ffed2507280   t.name[256]
|   t    |
+--------+
|   e    |
+--------+
|   s    |
+--------+
|   t    |
+--------+
|   f    |
+--------+
|   i    |
+--------+
|   l    |
+--------+
|   e    |
+--------+
|   \0   |
+--------+
...

你把这样的内存数据保持小端字节序一个字节一个字节写到硬盘上,然后你需要用的时候一个字节一个字节的读出来,接着还按照你机器的字节序套用到上面这个结构体格式里,你得到的还是上面的数据。这样你就保证了写出和读入的一致性。(当然保证跨平台跨硬件的一致性不是那么容易的事情)。

然后我再回答一下这个问题:

还有另一个问题:代码中判断 dirbuf.d_ino ==0 是什么意思?书上有这么一句:“如果某个目录位置当前没有使用(因为删除了一个文件),则它的i结点编号为0,并跳过该位置”,位置没有使用 是什么意思,不怎么理解,求指教~

答:你问题中的代码只是一个示意代码,并不一定真的是文件系统的逻辑代码,但是这个逻辑在一些简单的目录结构上没问题。一般一个目录节点上保存着其下面的文件的位置信息。假设一个目录的内容是这样的线性表中:

+---------+-------------------------+
| ino=100 | name="aaa"              |
+---------+-------------------------+
| ino=101 | name="bbb"              |
+---------+-------------------------+
| ino=102 | name="ccc"              |
+---------+-------------------------+
| ino=103 | name="ddd"              |
+---------+-------------------------+

假设我们删除ccc这个文件,我们可以把ccc那行整体删除,也可以把ino=102直接改成ino=0,让0表示这一行信息无效,读的时候不要读,写的时候可以被回收使用。

+---------+-------------------------+
| ino=100 | name="aaa"              |
+---------+-------------------------+
| ino=101 | name="bbb"              |
+---------+-------------------------+
| ino=0   | name="ccc"(无效)       |
+---------+-------------------------+
| ino=103 | name="ddd"              |
+---------+-------------------------+

所以在这样的设计下if (ino == 0)的判断逻辑可以判断目录中某个位置是否有效。这是只是一个逻辑设计而已,代码首先要保证自洽严谨,至于你认为这个设计好不好那是另一个问题。

又想到一个问题。。:linux文件的第一个字节就是文件的i结点编号吗?不然read函数是如何确定把文件的i节点编号放到结构体的d_ino成员上呢?

答:不是。文件的inode一般是一个位置信息。这里涉及到不同文件系统的具体设计,不同文件系统有不同的设计,你可以简单理解为[startblock, offset]的组合。当然实际上不是这么简单,但是道理是一样的,理论上inode号应该能通过拆解和计算让你定位你要找的inode的位置。这不是一个保存在文件元数据里的数据,它只是一个坐标。比如地球上一个坐标(39° N, 116° E),你可以把这个坐标合并到一个64位数据里,比如高四字节存维度,低四字节存经度,解析时将这个64位数拆解得到坐标,直接定位。比如这个定位的位置是一个大楼,你觉得需要这个大楼头顶插个牌子写上自己的经纬度才能找到吗?当然不是。


就回答到这吧,后面你补充的问题我没有读。更多关于操作系统和文件系统的问题,可参阅:

首先,你担心的结构体对齐的问题,这里会不会出问题关键在于Dirent *readdir(DIR *dp)函数里面的dp指向数据是哪里来的,以及这段程序是怎么编译的。

这里我们假设有两台机器,A和B,其中A的B的libc和kernel版本都不一样,而且DIRSIZ长度定义也不一样。那么以下场景就可能会出现问题:

1.在A上编译了readdir,拷贝到B上使用,反之一样,B上编译了readdir,拷贝到A上使用。

2.在A上读取了DIR *dp数据,没有经过处理,直接发送到B上经过readdir处理,反之一样。

这里面就是系统版本的兼容性问题了,代码一旦编译成库,内核或者可执行程序,那么对于数据结构的约定就固定了,所以使用相同版本的数据结构(头文件定义)和体系结构约定(比如x86和ARM大小端或者位数不同可能不同导致相同数据结构最后编译器约定结构并不一致)的相关组件(库,内核和可执行程序)必须一致才能工作正常,否则就可能出现问题。

这个道理反过来说也挺简单的,你编译一个32位程序,在64位系统上肯定是不行的。ARM的在X86上也是不行的,这是显然的。这个大家都懂,但是实际上,即使是相同体系结构,软件版本不同,程序兼容性也一样不太可靠。这也是正常的,现在的windows也没办法直接运行古老的dos命令行程序了。

但是在Linux这个事情显得尤其有点麻烦,因为无论是库还是kernel版本都太多,组合起来可能性很多,所以软件兼容性显得尤其困难。

这也是为什么大部分linux软件组件都不采用binary发布,而采用源码包方式发布。就是为了确保“本机编译,本机使用”。即使是发行版的Linux软件,采用了软件包管理器,进行二进制包的安装和部署,那也是有严格的版本限制。你可以试试安装一个比较早期的发行版本linux,看看能安装的软件包都有什么,都是同时期的对应版本,不会有最新的版本的软件的。因为这些安装包也都是发行版维护公司和组织在对应版本的维护期内编译和生成的。如果要使用比较新的版本找到对应软件版本但是系统版本不符的软件包进行安装,都可能会出现不兼容的问题的。

所以作为系统kernel和公用库来说,保持结构稳定是非常重要的,可以大量减少兼容性问题。

然后关于你做的那个实验,BCD所在的空间叫做padding,这部分是被编译器因为对齐而浪费的空间,如果你想存取这部分空间可以这样:

char *padding = &t1.c + 1  
printf("B: %c, C: %c, D: %d", padding[0], padding[1], padding[2])

这种操作一般比较危险,因为不同编译器的对齐方式可能并不一致,如果你不想让编译器留空出来,你需要使用pack标记:

struct __attribute__((__packed__))  test
    char c;
    int i;        
} t1;

这样,c和i中间就不会有空间了。

但是这样可能会让处理器效率降低,一般用于内存紧张的场合。所以不推荐,还是推荐平常就整理好数据结构,尽量少留空,比如:

typedef struct {
char a;
char *b;
char c:
int *d;
} shit;