tin*_*ast 29 c linux glibc variable-length-array restrict-qualifier
我知道restrictC中的限定符指定两个指针指向的内存区域不应重叠。据我了解,Linux(不是 SUS)原型看起来memcpy像 -
void* memcpy(void *restrict dest, const void *restrict src, size_t count);
Run Code Online (Sandbox Code Playgroud)
然而,当我查看man7.org/memcpy时,声明似乎是 -
void *memcpy(void dest[restrict .n], const void src[restrict .n], size_t n);
Run Code Online (Sandbox Code Playgroud)
我的问题是 -
.前面的是什么n意思?我熟悉可变长度数组声明。变量的 for是否.出现在数组指定之后?这是标准的一部分吗?Wei*_*hou 36
TLDR:这是在 Linux 邮件列表的讨论中创建的一种临时语法,用于在声明变量之前表达 VLA 的大小, in.表示.n引用n当前函数声明中的参数,但n可能出现在当前函数声明之后声明的参数。他们还将通常的int a[restrict n]参数声明扩展为void类型。我不知道在官方文档中哪里可以找到这样的语法,但邮件列表包含所有详细信息。
memcpyLinux 库函数手册中语法的更改是由commit c64cd13e引入的。提交消息被逐字复制到此处以供参考。
各个页面:概要:在“void *”函数参数中使用 VLA 语法
对于 void * 也使用 VLA 语法,即使它有点奇怪。
不可否认,从 C 语言的角度来看,这很奇怪,因为 whilevoid f(int n, int[restrict n])是有效的 VLA 语法,void f(int n, void[restrict n])并不是因为我们不允许有void.
对于.之前的内容n,如果我们深入挖掘,我们可以从邮件列表中找到该主题。linux-man
让我们举个例子:
Run Code Online (Sandbox Code Playgroud)int getnameinfo(const struct sockaddr *restrict addr, socklen_t addrlen, char *restrict host, socklen_t hostlen, char *restrict serv, socklen_t servlen, int flags);和一些转换:
Run Code Online (Sandbox Code Playgroud)int getnameinfo(const struct sockaddr *restrict addr, socklen_t addrlen, char host[restrict hostlen], socklen_t hostlen, char serv[restrict servlen], socklen_t servlen, int flags); int getnameinfo(socklen_t hostlen; socklen_t servlen; const struct sockaddr *restrict addr, socklen_t addrlen, char host[restrict hostlen], socklen_t hostlen, char serv[restrict servlen], socklen_t servlen, int flags);(我不确定我是否使用了正确的 GNU 语法,因为我自己从未使用过该扩展。)
上面的第一个转换是明确的,尽可能简洁,它唯一的问题是它可能会使实现变得过于复杂。我不认为前向使用参数的大小对于人类读者来说是一个太大的解析问题。
我个人认为第二种形式并不可怕。能够从左到右、自上而下地阅读代码对于更复杂的示例很有帮助。
第二个是不必要的长和冗长,并且对于人类读者来说,分号与逗号不太容易区分,这可能会非常令人困惑。
Run Code Online (Sandbox Code Playgroud)int foo(int a; int b[a], int a); int foo(int a, int b[a], int o);这两者对于编译器来说非常不同,但对于人眼来说却非常相似。我不喜欢它。它允许更简单的编译器这一事实不足以克服可读性问题。
这是真的,我可能会将它与逗号和/或语法突出显示一起使用。
我想我更喜欢将前向使用语法作为非标准扩展——或者标准但可选的语言功能——以避免强迫小型编译器实现它,而不是在所有编译器中标准化 GNU 扩展。
第二种形式的问题是:
- 它不是 100% 向后兼容(尽管可能没问题),因为以下代码的语义发生了变化:
整数n; int foo(int a[n], int n); // 指的是不同的n!
当带有“n”的变量在范围内时,为新编译器编写的代码可能会被旧编译器误解。
对于 C 来说,拥有向后引用通常是全新的,并且可能需要更改解析器以允许这种情况
然后,编译器或工具还必须处理丑陋的极端情况,例如相互引用:
int foo(int (*a)[sizeof(*b)], int (*b)[sizeof(*a)]);
我们可以考虑新的语法,例如
int foo(char buf[.n], int n);
就我个人而言,我更喜欢前向声明的概念简单性以及 GCC 中已经存在的事实,而不是任何替代方案。我也不介意新的语法,但必须更精确地定义规则以避免上述问题。
根据我的理解,这基本上意味着这.是一种在声明之前引用 VLA 数组大小参数的方法,一个用例是处理相互引用。
有一个后续线程指出,
我对语法没问题,但我不确定这将如何工作。如果稍后才确定类型,您仍然需要更改解析器(某些 C 编译器在解析期间进行类型检查和折叠,因此需要在解析期间了解类型),并且仍然存在相互依赖性的问题。
我们考虑过使用这种语法
int foo(char buf[.n], int n);
因为它是新语法,这意味着我们可以将大小限制为参数名称,而不是允许任意表达式,这样就可以减少前向引用的问题。它也与初始化程序中的指示符一致,并且还可以扩展以注释灵活的数组成员或在结构中存储指向数组的指针:
结构体 { int n; 字符 buf[.n]; };
结构体 { int n; 字符 (*buf)[.n]; };
当然,也有人反对,我想SO社区的很多人都会同意,
我唯一强烈关心的是这一点:
手册页不应使用
- 非标准语法
- 不可移植语法
- 不明确的语法(即对于不同的编译器或在不同的上下文中可能具有不同含义的语法)
- 对于某些广泛使用的编译器集合(例如 GCC 或 LLVM)可能无效或危险的语法
对于这两个问题,VLA 表示法似乎是 C23 设计原则的目标,即“API 应尽可能自记录”。请参阅编程语言 C-C23 宪章。
点符号没有出现在2023 年 4 月的 C23 草案中,我推测它是该标准未来修订的愿望清单项目。点表示法的作者公开承认它不是有效的语法,并在1eed67e给出了他选择它的原因
该表示法似乎起源于 Linux 开发社区,其在已发布man-pages文档中的使用似乎有些推测性。它是通过提交1eed67e(提交消息是这个问题的一个比我能管理的更好的答案)和c64cd13以及语言“对 void * 使用 VLA 语法,即使它有点奇怪。 ”引入的。
语言“即使它有点奇怪”告诉我,作者希望该语法最终可以考虑包含在 C 标准中,因为他没有引用任何权威来源,例如草案或编译器实现。
就可变长度数组功能而言,自 C90 以来它已作为扩展在 GCC 中得到支持,自 C99 以来已作为标准:GCC 可变长度文档。AFAIK 使用的点符号是尚未在任何 GCC 版本中实现的手册页。
glibc 使用void *撰写本文时(2023 年 9 月 3 日)头文件中的表示法。