这是代码:
#include<stdio.h>
#include<string.h>
int main()
{
char *s = "name";
int n = strlen(s);
int i;
s = &s[n+1];
for(i=0; i<=n; i++)
{
printf("%d %c",i,*s);
s++;
}
return 0;
}
输出:
0 %1 d2 3 %4 c
我无法理解输出。为什么它的打印%虽然没有转义顺序。
答案 0 :(得分:4)
这一行s = &s[n+1];
导致你的指针指向不知名的中间位置。之后,你开始从中读取随机垃圾。显然随机垃圾包含一些%
个字符。
答案 1 :(得分:1)
首先分配s = &s[n+1];
然后使用*s
访问printf中的超出内存。根据C标准,代码在Undefined行为下运行。
s[]
的最大索引可以是包含\0
的字符串的长度。请记住索引值从0
开始到(array/string)-1
你的字符串存储在内存中,如:
s 23 24 25 26 27 28
+----+ +----+----+----+----+----+----+
| 23 | | n | a | m | e | \0 | ? |
+----+ +----+----+----+----+----+----+
0 1 2 3 4 5
s points to string "name"
string length of "name" is 4
length("name") + 1 = 5
? means garbage values
在表达式s = &s[n+1];
中,n + 1
是五个5
,指向"name"
字符串的已分配内存之外的位置。在printf语句中,您使用{{1}访问内存解除引用运算符导致无效的内存访问,并且此代码在运行时的行为为Undefined。这就是您在不同执行时编码表现不同的原因。
您的代码编译正确,因为语法上它是正确的,但在运行时,操作系统内核可以检测到未分配的内存。这可能导致OS内核向您的进程发送信号核心转储,从而导致异常。 (有趣的是:由于OS检测到进程的内存权限违规 - 对有效内存的无效访问给出:SIGSEGV并且访问无效地址给出:SIGBUS)。在值得的情况下,您的程序可以执行而不会失败,它会产生垃圾结果
答案 2 :(得分:0)
s = &s[n+1];
是超出范围的分配。 s [n]的值为'\ 0',此后s[n+1]
将有一些垃圾值。
上面显示的分配是将s[n+1]
的基地址分配给s
,稍后您将尝试从此新s
打印值,因此所有值都将是垃圾。超出范围是未定义的行为。
答案 3 :(得分:0)
s = &s[n+1];
使s
指向未知内存。之后引用s
会调用未定义的行为,当您在printf
中访问时,可能会发生任何事情。
答案 4 :(得分:0)
未定义的行为,因为无论何时执行s[n+1]
,其中n
是字符串的长度。您也可以再次将此新地址分配到s
。从这个位置开始访问每个索引都会导致行为异常,因为您不知道这些位置的内容是什么,您是否可以访问它。
您可以尝试在您定义的字符串之后立即定义另一个字符串。
char *s = "name";
char *d = "hello test";
在这种情况下,如果编译器恰好将字符串“name”后面的字符串存储在只读区域中,则最终可能会打印字符串“hello test”中的字符。这不是保证。
最重要的是,这段代码不正确并导致未定义的行为。
答案 5 :(得分:0)
您正在将s的指针更改为屏幕的末尾,这就是您有一些随机垃圾的原因。
例如,如果您将主要内容更改为
void foo(char *str)
{}
int main()
{
char *s = "name";
int n = strlen(s);
int i;
s = &s[n+1];
foo("Test");
for(i=0; i<=n; i++)
{
printf("%d %c\n",i,*s);
s++;
}
return 0;
}
我认为它会显示测试
但你不应该这样做。
答案 6 :(得分:0)
你问:
为什么打印
%
虽然没有转义序列。
当您尝试从格式字符串本身打印%
时,仅适用于打印%
的转义序列。那就是:
printf("%% %d\n", 1);
/* output:
% 1
*/
当它作为格式转换的参数提供时,无需转义它:
printf("%s %c\n", "%d", '%');
/* output:
%d %
*/
您的程序调用未定义的行为,因为您使s
指向一个指向它的最后一个有效对象(允许),然后您正在读取它(以及之后)打印循环(不允许)。由于它是未定义的行为,它可以什么都不做,可能会崩溃,或者它可以创建你看到的输出。
您可以从以下程序获得您获得的输出:
#include <stdio.h>
int main () {
const char *s = "%d %c";
int i;
for (i = 0; i < 5; ++i) {
printf("%d %c", i, *s);
s++;
}
puts("");
return 0;
}
/* output is:
0 %1 d2 3 %4 c
*/
如果每次调用printf
之间有分隔符,则此输出不会那么奇怪。如果我们在每次调用printf
后在输出的末尾添加换行符,则输出变为:
0 %
1 d
2
3 %
4 c
正如您所看到的,它只是输出s
指向的字符串,其中打印的每个字符前面都有该字符的索引位置。
答案 7 :(得分:0)
在您的计划中:
n = 4;
s = &s[n + 1] = &s[5];
指针s
指向未初始化的内存,因此输出应不确定!
答案 8 :(得分:0)
正如许多人所指出的,你已经将指针移动到静态字符串的末尾。
由于printf格式字符串也是静态字符串,因此静态字符串“name”旁边的内存可能是printf格式字符串。但是这不能保证,你也可以打印垃圾内存。