根据C标准,printf()的签名是:

int printf(const char * restrict format, ...);

据我所知,restrict的含义是format将是指针生命周期内指向数据的唯一引用.由于我不完全理解的原因,这使得优化成为可能.但是,这是否意味着如果我重复使用格式字符串的内存作为参数,就会调用未定义的行为?即使,据我所知,格式字符串不一定是字符串文字?

static const char str[] = "%sHello\n";
printf(str, str + 2); // Hello\nHello\n or UB?

我知道该实现可能会对相同或相同结尾的字符串文字重复使用内存:

"foo" + 1 == "oo"; // Might be true

这是否意味着以下情况:

printf("%sHello\n", "Hello\n");

如果实现使两个字符串字面值共享内存,从而违反了restrict限制,可能会以一种无意义的方式运行?

推荐答案

据我所知,restrict的含义是format将是指针生命周期内指向数据的唯一引用.

不完全是.这意味着,如果以任何方式(通过format或其他方式)修改format字符串1,则printf必须仅通过基于format的其他表达式访问它(因此调用者不得传递另一个参数,该参数将导致printf通过该参数访问格式字符串),根据C 2018 6.7.3.1.如果格式字符串中的任何内容在printf调用期间都不会被修改,则有什么其他指向它的指针都无关紧要.

我认为唯一有意义的方法是使用n转换说明符,该说明符表示对应的参数是一个指向带符号整数的指针,其中写入了这个对printf的调用到目前为止写入输出流的字符数.因此,如果format不是restrict-qualified,您可以编写以下两个printf调用:

int n;
memcpy(&n, "x%n", 4);
printf((char *) &n, &n);  // Would store 1 in `n`, since “x” had been written.

const char String[] = "Hello, world.\n%n";
int *p = malloc(sizeof String);
memcpy(p, String, sizeof String);
printf(p, p);  // Would store int `14` at `p`.

前者是合法的,因为任何对象都可以通过字符类型读取,据推测printf可以做到这一点.后者是合法的,因为动态存储器的有效类型是可延展的,因此在将p用作字符串之后将其作为int写入是定义的.

显然,如果%n导致写入printf可能尚未用于格式字符串的内存,这将是有问题的.同样,这是在我们假设字符串没有restrict限定的上下文中.如果是这样的话,就不会定义以这种方式使用%n的行为.

(n也可以与修饰语一起使用,如在%hhn中写char而不是int,但这不影响上面的分析.)

脚注

1从技术上讲,format上的任何对象based,实质上是format指向的数组对象的所有元素(如果format指向数组中间,则包括数组中较早的元素).

C++相关问答推荐

使用sd-设备列举设备导致seg错误

自定义malloc实现上奇怪的操作系统依赖行为

找出文件是否包含给定的文件签名

仅在给定的大小和对齐方式下正确创建全局

如何知道我是否从非阻塞套接字读取所有内容

双指针指向常量双指针的指针类型赋值不兼容

试图从CSV文件中获取双精度值,但在C++中始终为空

为什么电路板被循环删除?

递归打印二维数组(C编程)

GetText不适用于包含国际字符的帐户名称

为什么我无法访问C语言中的文件

表达式x&;&;(~x)应该返回1还是0?它依赖于编译器吗?

是否有单独的缓冲区用于读写库调用?

';malloc():损坏的顶部大小';分配超过20万整数后

`%%的sscanf无法按预期工作

我正在使用 klib 库 我可以使用 (khash) KHASH_SET_INIT_INT64() 负值作为键.因为我在头文件中看到它使用 unsigned long int

SSE 向量与 Epsilon 的比较

clion.我无法理解 Clion 中发生的 scanf 错误

仅使用其内存地址取消引用 C 中的 struct

将字节/字符序列写入标准输出的最简单形式