找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 606|回复: 3

【C】变长数组成员

[复制链接]
发表于 2023-2-15 18:54:34 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 usr 于 2023-2-15 19:06 编辑

这篇文章中我们来探讨一下“变长数组成员(Flexible Array Member,FAM)”
一、声明
变长数组成员是这样声明的:

  1. struct st_s
  2. {
  3.         int i;
  4.         int a[]; // This is a FAM.
  5. };
复制代码

注意:变长数组成员只能出现在结构体的最底部。
这样是不行的,VC会产生编译错误(但是clang,gcc会通过编译):

  1. struct st_s
  2. {
  3.         int a[]; // Compiling Error by using VC19.
  4.         int i;
  5. };
复制代码

我们来看看C98标准的说明:
15 As a special case, the last element of a structure with more than one named member may
have an incomplete array type. This is called a flexible array member, and the size of the
structure shall be equal to the offset of the last element of an otherwise identical structure
that replaces the flexible array member with an array of unspecified length.95) When an
lvalue whose type is a structure with a flexible array member is used to access an object,
it behaves as if that member were replaced with the longest array, with the same element
type, that would not make the structure larger than the object being accessed; the offset of
the array shall remain that of the flexible array member, even if this would differ from
that of the replacement array. If this array would have no elements, then it behaves as if it
had one element, but the behavior is undefined if any attempt is made to access that
element or to generate a pointer one past it.

C89标准支持FAM。
二、使用
FAM的使用如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>

  4. struct st_aaa
  5. {
  6.         int i;
  7.         char a[0];
  8. };

  9. int main()
  10. {
  11.         struct st_aaa * pab;
  12.         pab = calloc(1, sizeof(struct st_aaa) + sizeof(char) * 3);
  13.         *(pab->a) = 0;
  14.         free(pab);
  15.         return 0;
  16. }
复制代码

以上代码,我们用calloc函数申请了一块内存,这块内存的大小等于struct st_aaa的大小加上尾部三个char的大小。
然后我们把在结构体尾部追加的三个char的第一个设置为'\0'。
这里我们需要当心,FAM是变长数组,但不是普通指针。如果想改变FAM的指向,就会发生错误:

  1. struct st_aaa
  2. {
  3.         int i;
  4.         char a[];
  5. };

  6. int main()
  7. {
  8.         struct st_aaa * pab;
  9.         pab = calloc(1, sizeof(struct st_aaa) + sizeof(char) * 3);
  10.         pab->a = NULL; // Compiling error.
  11.         free(pab);
  12.         return 0;
  13. }
复制代码

三、gnu编译器的FAM扩展:
在gnu c编译器中,FAM的写法有两种。第一种是ISO/ANSI标准,第二种是gnu c拓展:

  1. struct st_aaa
  2. {
  3.         int i;
  4.         char a[]; // Standard.
  5. };

  6. struct st_bbb
  7. {
  8.         int i;
  9.         char a[0]; // Gnu extension.
  10. };
复制代码

gnu扩展就是在数组声明中添加0.它看起来是0长度数组,其实是变长数组。gnu拓展也能被VC19支持。
gnu编译器的FAM扩展使用方法和标准FAM一致。clang编译器也支持gnu扩展。
四、FAM使用细节:

  1. struct s { int n; double d[]; };
  2. struct ss { int n; double d[1]; };
  3. // 测试
  4. sizeof (struct s)
  5. offsetof(struct s, d)
  6. offsetof(struct ss, d)
复制代码

(1)以上代码中,注释“测试”以下的三行代码执行结果相同。
如果sizeof(double)等于8,则执行以下代码后:

  1. struct s { int n; double d[]; };
  2. struct s *s1;
  3. struct s *s2;
  4. s1 = malloc(sizeof (struct s) + 64);
  5. s2 = malloc(sizeof (struct s) + 46);
复制代码

(2)如果malloc执行成功,以下代码中结构体的大小等于以上代码中malloc声请的结构体大小。

  1. struct { int n; double d[8]; } *s1;
  2. struct { int n; double d[5]; } *s2;
复制代码

五、FAM的实用例子:
我们可以使用FAM制造一个线性表数据结构,代码如下:

  1. #define CRT_SECURE_NO_WARNINGS
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <stdbool.h>
  6. #include <assert.h>

  7. typedef struct st_LinearTable
  8. {
  9.         size_t length;
  10.         char array[];
  11. } LTBL, * P_LTBL;

  12. // ADT interface:

  13. P_LTBL CreateLTBL(size_t length)
  14. {
  15.         P_LTBL p;
  16.         p = (P_LTBL)calloc(1, sizeof(LTBL) + length);
  17.         if (p)
  18.                 p->length = length;
  19.         return p;
  20. }

  21. void DeleteLTBL(P_LTBL ptbl)
  22. {
  23.         assert(ptbl);
  24.         ptbl->length = 0;
  25.         free(ptbl);
  26. }

  27. char * ContentLTBL(P_LTBL ptbl)
  28. {
  29.         assert(ptbl);
  30.         return ptbl->array;
  31. }

  32. size_t LengthLTBL(P_LTBL ptbl)
  33. {
  34.         assert(ptbl);
  35.         return ptbl->length;
  36. }
  37. // Program entry.

  38. int main()
  39. {
  40.         P_LTBL pstr;
  41.         pstr = CreateLTBL(strlen("Hello") + 1);

  42.         strcpy_s(ContentLTBL(pstr), LengthLTBL(pstr), "Hello");
  43.        
  44.         printf("%s\n", &ContentLTBL(pstr)[1]); // Print ello.

  45.         DeleteLTBL(pstr);
  46.         return 0;
  47. }
复制代码

六、参考资料
ISO/IEC C Programming Language Standard(C89:1990)
https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
https://www.geeksforgeeks.org/flexible-array-members-structure-c/
回复

使用道具 举报

发表于 2023-2-22 09:55:39 | 显示全部楼层
这些动态特性加到c上其实很冗余,用c++就直接搞定了。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2023-2-23 17:28:41 | 显示全部楼层
元始天尊 发表于 2023-2-22 09:55
这些动态特性加到c上其实很冗余,用c++就直接搞定了。

你是不是觉得C应该更“纯洁”一点?
回复 赞! 靠!

使用道具 举报

发表于 2023-2-23 19:27:34 | 显示全部楼层
本帖最后由 元始天尊 于 2023-2-23 19:32 编辑
usr 发表于 2023-2-23 17:28
你是不是觉得C应该更“纯洁”一点?


现在各个语言都在互相学习更有优势的语法,语言作者都不在乎纯洁,我更不在乎了。
编程开发第一要义是便于开发维护和重构。
c++动态特性很多,可以互相搭配
c的话,单纯一个动态特性实际用途有限,c更适合处理底层数据。
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-24 22:15 , Processed in 0.033091 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表