为什么在C中的%ld中打印指针会产生这个结果?

时间:2017-05-09 14:05:56

标签: c

为什么在C中的%ld中打印指针会产生这个结果?

 char txt[] = "abcdefghij";
 char *pointer = &txt[1];

 printf("%ld\n", pointer - txt);
 printf("%d\n", pointer - txt);

这两个都打印1,因为它总是以1的差异减去大数,
例如3032 - 3031 但为什么我会得到这些数字?

4 个答案:

答案 0 :(得分:3)

C标准允许减去指向同一对象的两个指针:

来自section 6.5.6

  

9 当减去两个指针时,都指向同一个数组对象的元素,或者指向数组最后一个元素的元素   宾语; 结果是两者下标的差异   数组元素。结果的大小是实现定义的,和   它的类型(有符号整数类型)是ptrdiff_t中定义的   stddef.h标题。如果结果在对象中无法表示   该类型,行为未定义。换句话说,如果   表达式P和Q分别指向第i和第j个元素   对于数组对象,表达式(P) - (Q)具有提供的值i-j   该值适合ptrdiff_t类型的对象。而且,如果   表达式P指向数组对象的元素或一个元素   超过数组对象的最后一个元素,表达式Q指向   到同一个数组对象的最后一个元素,表达式   ((Q)+1) - (P)具有与((Q) - (P))+ 1相同的值和 - ((P) - ((Q)+1)),   如果表达式P指向过去的一个,则其值为零   数组对象的元素,即使表达式(Q)+1没有   指向数组对象的元素。

评估pointer - txt时,数组txt会衰减到指向第一个元素的指针,即&txt[0]。由于pointer指向txt[1],因此您实际上拥有&txt[1] - &txt[0]。如上所述,减法的结果是下标之间的差异,即1。

作为用于正常显示该结果,%d%ld格式说明为printf是不适合于ptrdiff_t,这是结果的类型。如上所述,此类型的大小是实现定义的。使用错误的格式说明符printf会调用undefined behavior

所以你需要使用t修饰符,它告诉printf给定的整数参数类型为ptrdiff_t

printf("%td\n", pointer - txt);

答案 1 :(得分:2)

指针初始化的方式,pointer将指向txt之后的一个位置,这意味着由于指针算术,差异为1

答案 2 :(得分:1)

首先请注意,如果两个指针位于同一个数组中,或者超过数组末尾,则只能取两个指针之间的差异。否则行为未定义。你在这一点上是顺从的。

但您符合您选择的格式说明符。因此,程序的行为是 undefined 。真的,我想看看

ptrdiff_t c = pointer - txt;

接着是

printf("%td\n", c);

ptrdiff_t是两个指针之间差异的完美类型,%td是该类型的正确格式说明符。这是完美的便携式C(如果我可以这么说的话)。

答案 3 :(得分:0)

  

但为什么我会得到这些数字?

仅供参考,我写了以下内容:

#include <stdio.h>
#include "dumper.h"

int main( void )
{
  char txt[] = "abcdefghij";
  char *ptr = &txt[1];

  char *names[] = {"txt", "ptr"};
  void *addrs[] = {txt, &ptr};
  size_t sizes[] = {sizeof txt, sizeof ptr};

  /**
   * Use %p to print out pointer values.  %p expects the corresponding
   * argument to have type `void *`.  Because of how printf works,  
   * this is one of the few places were you really do need to explicitly
   * cast pointer values to `void *` (if they aren't already `void *`)
   */
  printf( "txt = %p\n", (void *) txt );
  printf( "ptr = %p\n", (void *) ptr );

  /**
   * Use %td to print out ptrdiff_t values.
   */
  printf( "ptr - txt = %td\n", ptr - txt );
  putchar( '\n' );

  dumper( names, addrs, sizes, 2, stdout );

  return 0;
}

dumper是我写的一个实用程序,用于显示内存中对象的布局。运行此代码,我得到以下输出:

txt = 0x7fff7f137380
ptr = 0x7fff7f137381
ptr - txt = 1

           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
            txt  0x7fff7f137380   61   62   63   64    abcd
                 0x7fff7f137384   65   66   67   68    efgh
                 0x7fff7f137388   69   6a   00   00    ij..

            ptr  0x7fff7f137378   81   73   13   7f    .s..
                 0x7fff7f13737c   ff   7f   00   00    ....

txt是从地址char开始存储的0x7fff7f137380的11个元素数组,其中包含char个值"abcdefghij\0"的序列。 ptr是从地址char开始存储0x7fff7f137378的指针,它存储txt[1]0x7fff7f113781)的地址(x86是little-endian,所以从最低有效字节开始存储多字节值。

但请等一下,如果txt数组,如何从指针中减去它?

除非它是sizeof或一元&运算符的操作数,或者是用于初始化声明中的字符数组的字符串文字,否则类型为表达式 &#34; T&#34;的N元素数组;将被转换(&#34;衰减&#34;)到类型为&#34的表达式;指向T&#34;的指针,并且表达式的值将是第一个元素的地址。阵列。

在表达式ptr - txt中,txt不是sizeof或一元&运算符的操作数,并且它不是用于初始化另一个运算符的字符串文字在一个声明中的数组,所以它'&#34;衰变&#34;来自char&#34;的类型&#34; 11元素数组的表达式to&#34;指向char&#34;的指针,其值为txt[0]的地址。

好。为什么txtptr地址的数字如此庞大?在x86和x86-64下,程序布局看起来像这样:

              +-----------------------------+
High Address: |   Command-line arguments    |
              |  and environment variables  |
              +-----------------------------+
              |            Stack            |
              |              |              |
              |              V              |
              |                             |
              |              ^              |
              |              |              |
              |            Heap             |
              +-----------------------------+
              |     Uninitialized Data      |
              +-----------------------------+
              |      Initialized Data       |
              +-----------------------------+
              |        Program Text         |
 Low Address: |       (machine code)        |
              +-----------------------------+

txtptr都是auto个变量,在x86上表示它们的空间是从堆栈中分配的,它从高地址开始。在我的系统上,指针是64位宽。

现在,在这种情况下,txt[0]txt[1]的地址相差1是正确的。但是,让我们尝试一下 - 让我们改变类型txtint []ptrint *

#include <stdio.h>
#include "dumper.h"

int main( void )
{
  int txt[] = {0,1,2,3,4,5,6,7,8,9};
  int *ptr = &txt[1];

  char *names[] = {"txt", "ptr"};
  void *addrs[] = {txt, &ptr};
  size_t sizes[] = {sizeof txt, sizeof ptr};

  printf( "txt = %p\n", (void *) txt );
  printf( "ptr = %p\n", (void *) ptr );

  printf( "ptr - txt = %td\n", ptr - txt );
  putchar( '\n' );

  dumper( names, addrs, sizes, 2, stdout );

  return 0;
}

运行此命令,我得到以下输出:

txt = 0x7fff317fa850
ptr = 0x7fff317fa854
ptr - txt = 1

           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
            txt  0x7fff317fa850   00   00   00   00    ....
                 0x7fff317fa854   01   00   00   00    ....
                 0x7fff317fa858   02   00   00   00    ....
                 0x7fff317fa85c   03   00   00   00    ....
                 0x7fff317fa860   04   00   00   00    ....
                 0x7fff317fa864   05   00   00   00    ....
                 0x7fff317fa868   06   00   00   00    ....
                 0x7fff317fa86c   07   00   00   00    ....
                 0x7fff317fa870   08   00   00   00    ....
                 0x7fff317fa874   09   00   00   00    ....

            ptr  0x7fff317fa848   54   a8   7f   31    T..1
                 0x7fff317fa84c   ff   7f   00   00    ....

txtptr的地址差异为4,但ptr - txt仍然会产生1.为什么会这样?

指针算法将指向对象的大小考虑在内 - 如果p是指向T的指针,则p + 1将生成下一个对象的地址键入T ,而不是下一个字节的地址。无论每个&txt[n] - &txt[n-1]是1,4,8,16,32还是128字节宽,txt[n] 总是会产生1