我知道C中的restrict限定符指定由两个指针指向的内存区域不应重叠.据我所知,memcpy的Linux(不是SUS)原型看起来像-

void* memcpy(void *restrict dest, const void *restrict src, size_t count);

然而,当我看着man7.org/memcpy份声明时,似乎是-

void *memcpy(void dest[restrict .n], const void src[restrict .n], size_t n);

我的问题是-

  1. 这种语法是什么时候引入的?C99或更高版本,或者这是某个GNU扩展?
  2. n之前的.是什么意思?我熟悉可变长度数组声明.变量的.是否出现在数组规范之后?这是标准的一部分吗?

推荐答案

TLDR:这是在Linux邮件列表的讨论中创建的特殊语法,用于在声明变量之前表示VLA的大小,.n中的.表示n引用当前函数声明中的参数,但n可能出现在当前声明的参数之后.他们还将通常的int a[restrict n]参数声明扩展为void类型.我不知道在官方文档中哪里可以找到这样的语法,但邮件列表中有所有的细节.


LINUX库函数手册中对memcpy语法的更改是在commit c64cd13e版中引入的.提交消息被逐字复制到此处以供参考.

各个页面:概要:在‘void*’函数参数中使用VLA语法

也可以使用VLA语法来表示空*,尽管它会更奇怪一些.

诚然,从C语言的Angular 来看,这已经够奇怪的了,因为虽然void f(int n, int[restrict n])是有效的VLA语法,但void f(int n, void[restrict n])不是,因为我们不允许有void的array.

对于n之前的.,如果我们更深入地挖掘,我们可以从linux-man mailing list中找到this thread.

让我们举个例子:

    int getnameinfo(const struct sockaddr *restrict addr,
                    socklen_t addrlen,
                    char *restrict host, socklen_t hostlen,
                    char *restrict serv, socklen_t servlen,
                    int flags);

以及一些转变:

    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语法,因为我从未使用过 扩展我自己.)

上面的第一个转换是毫不含糊的,尽可能简洁, 唯一的问题是,它可能会使实现稍微复杂化 太多.我不认为超前-使用参数的大小会太过 对于人类读者来说,这是一个很大的解析问题.

我个人认为第二种形式并不可怕.能够阅读 代码从左到右、自上而下在更复杂的示例中很有帮助.

第二个是不必要的冗长和冗长,而分号不是 与逗号非常不同,对于人类读者来说,这可能非常 令人困惑.

    int foo(int a; int b[a], int a);
    int foo(int a, int b[a], int o);

这两个对于编译器来说非常不同,但又非常类似于 人类的眼睛.我不喜欢这个.事实上,它允许更简单的 编译器不足以克服可读性问题.

这是真的,我可能会将其与逗号和/或语法一起使用 突出显示.

我想我更喜欢将向前使用的语法作为非标准语法 扩展--或标准但可选的语言功能--以避免 迫使小型编译器实现它,而不是让GNU 在所有编译器中标准化的扩展.

第二种形式的问题是:

  • 它不是100%向后兼容的(尽管这可能是可以的),因为以下代码的语义发生了变化:

Int 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);

就我个人而言,我更喜欢Forward的概念简单性 声明和事实,这些已经存在于GCC的任何 另类 Select .我也不介意新的语法,但那是必须的 更准确地定义规则,以避免上述问题.

根据我的理解,这基本上意味着.是一种引用在声明之前使用的VLA数组大小参数的方法,一个用例是处理相互引用.

有一个follow-up thread,上面写着,

我对语法很满意,但我不确定这是如何工作的.如果 类型是以后才确定的,您仍然需要更改解析器 (一些C编译器在解析过程中执行类型判断和折叠,因此 需要在解析过程中知道类型),并且您仍然拥有 相互依赖的问题.

我们考虑过使用以下语法

Int foo(char buf[.N],int n);

因为它是新的语法,这意味着我们可以将大小限制为 参数的名称,而不是允许任意表达式, 这就减少了前向引用的问题.它也是 与初始值设定项中的指示符一致,也可以扩展 注释灵活的数组成员或存储指向数组的指针 在 struct 中:

Struct{int n;char buf[.N];};

Struct{int n;char(*buf)[.N];};

当然,也有objection个,我想很多SO社区的人都会同意,

我唯一非常关心的是这一点:

手册页不应使用

  • 非标准语法
  • 不可移植的语法
  • 不明确的语法(即,不同的编译器或不同的上下文中可能具有不同含义的语法)
  • 对于一些广泛使用的编译器集合,如GCC或llvm,语法可能是无效的或危险的

C++相关问答推荐

初始化char数组-用于初始化数组的字符串是否除了存储数组的位置之外单独存储在内存中

InetPton()函数无效的IP地址

如何将字符串argv[]赋给C中的整型数组?

如何使fputs功能提示错误输入并要求用户重新输入.程序停止而不是请求新的输入

将常量转换为指针会增加.数据大小增加1000字节

在C语言中,在数学运算过程中,为什么浮点数在变量中的行为不同

文件权限为0666,但即使以超级用户身份也无法打开

为什么我不能只在内存地址中添加一个int来寻址任何数组?

将uintptr_t添加到指针是否对称?

解决S随机内存分配问题,实现跨进程高效数据共享

在Linux上使用vscode和lldb调试用Makefile编译的c代码

我应该在递归中使用全局变量吗

从C中的函数返回静态字符串是不是一种糟糕的做法?

Valgrind用net_pton()抱怨

用C++高效解析HTTP请求的方法

隐藏测试用例无法在c程序中计算位数.

OpenGL 中的非渐变 colored颜色 变化

使用 SDL2 的 C 程序中的内存泄漏

窗口消息处理函数以某种方式更改了应保持不变的 int 变量的值

设置具有非零终止字符串的大整数