无法解释C中简单字符串操作的输出

时间:2013-07-18 05:59:21

标签: c string pointers

这是代码:

    #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

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

9 个答案:

答案 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格式字符串。但是这不能保证,你也可以打印垃圾内存。