0

这是代码:

    #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

我无法理解输出。为什么它的打印 % 虽然没有转义序列。

4

9 回答 9

4

这条线s = &s[n+1];导致您的指针指向不知名的地方。之后,您开始从中读取随机垃圾。显然,随机垃圾包括一些%字符。

于 2013-07-18T06:02:18.160 回答
1

首先分配s = &s[n+1];,然后使用 printf 访问超出范围的内存*s。根据 C 标准,代码在未定义的行为下运行。

最大索引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 语句中,您使用*Dereference 运算符访问内存会导致无效的内存访问,并且此代码在运行时的行为是未定义的。这就是您代码在不同执行时表现不同的原因。

您的代码可以正确编译,因为它在语法上是正确的,但是在运行时访问未分配的内存可以被操作系统内核检测到。这可能会导致操作系统内核向导致异常的进程发送信号核心转储。(有趣的是:当操作系统检测到进程违反内存权限时——对有效内存的无效访问会给出:SIGSEGV 并且对无效地址的访问会给出:SIGBUS)。在值得的情况下,您的程序可能会在没有任何失败的情况下执行,它会产生垃圾结果。

于 2013-07-18T06:02:32.627 回答
0

You are changing the pointer of s to the end of your screen, that's why you have some random garbage.

If for exemple you change your main to

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;
}

I think it will display test

But you should not do such thing.

于 2013-07-18T06:08:05.320 回答
0

s = &s[n+1];是越界赋值。s[n] 的值是 '\0' ,在这之后s[n+1]会有一些垃圾值。

上面显示的分配是分配s[n+1]to的基地址,s稍后您尝试从这个新的值打印值,s因此所有值都将是垃圾。越界访问是未定义的行为。

于 2013-07-18T06:02:48.743 回答
0
s = &s[n+1];

使s指向未知的记忆s此后引用会调用未定义的行为,当您在printf.

于 2013-07-18T06:03:00.807 回答
0

未定义的行为,因为无论何时执行,字符串的长度在s[n+1]哪里。n此外,您正在s再次分配这个新地址。从该位置开始访问每个索引将导致未定义的行为,因为您不知道这些位置的内容,并且您可以访问或不访问它。

您可以尝试在您定义的字符串之后立即定义另一个字符串。

char *s = "name";
char *d = "hello test";

在这种情况下,如果编译器恰好在只读区域中的字符串“name”之后立即存储字符串,那么您最终可能会打印字符串“hello test”中的字符。这不是保证的。

最重要的是,这段代码不正确并导致未定义的行为。

于 2013-07-18T06:03:38.990 回答
0

正如许多人所指出的,您已经将指针 s 移到了静态字符串的末尾。

由于 printf 格式字符串也是静态字符串,因此静态字符串“name”旁边的内存可能是 printf 格式字符串。然而,这并不能保证,您也可以打印垃圾内存。

于 2013-07-18T07:46:39.930 回答
0

您问:

%尽管没有转义序列,但为什么要打印。

%仅当您尝试%在格式字符串本身中打印 from时,打印 a 的转义序列才适用。那是:

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,其中打印的每个字符前面都有该字符的索引位置。

于 2013-07-18T06:24:21.600 回答
0

在你的程序中:

n = 4;
s = &s[n + 1] = &s[5];

指针s指向未初始化的内存,所以输出应该是不确定的!

于 2013-07-18T07:42:36.777 回答