在C中递增指针地址和值

int main (void){
    int num1=2;
    int *pnum=NULL;
    pnum=&num1;
    *pnum++;
    printf("%d",*pnum);
    
}

为什么此代码打印地址,而不是值?不 * 取消引用 pnum 吗?

回答

检查运算符的优先级:后缀一元运算符比前缀一元运算符绑定得更紧密,因此*pnum++等效于*(pnum++),而不是(*pnum)++

pnum++递增指针pnum并返回 的旧值pnum。递增指针使其指向数组的下一个元素。任何变量都可以被视为一个元素的数组,因此pnum指向位于num1is的单元素数组中第一个之后的元素,我将称之为num1_array[1]。指向数组末尾的指针是有效的,即经过最后一个元素的一个位置。取消引用该指针是无效的:那将是数组溢出。但计算指针是有效的。在 C 中构造无效指针是未定义行为,即使您没有取消引用它;但是这个指针是有效的。

*pnum++取消引用 的旧值pnum。因为那是一个指向 的指针num1,所以这个表达式是完全有效的,它的值是 的值num1。此时,任何半途而废的编译器都会警告该值未使用。如果您没有看到此消息,请配置您的编译器以打印更多警告:不幸的是,许多编译器默认接受错误代码而不是发出错误信号。例如,使用 GCC 或 Clang:

$ gcc -Wall -Wextra -Werror a.c
a.c: In function ‘main’:
a.c:6:5: error: value computed is not used [-Werror=unused-value]
    6 |     *pnum++;
      |     ^~~~~~~
cc1: all warnings being treated as errors

调用printf接收参数*pnum。我们之前看到,此时,pnum指向单元素数组的末尾num1_array[1]。这个指针是有效的,但由于它指向对象的末尾,解引用具有未定义的行为。在实践中,这通常要么崩溃,要么打印一些恰好位于特定内存位置的垃圾值。当您调试程序时,有一些工具可以帮助您提高无效指针导致崩溃的可能性,而不是默默地使用垃圾值。例如,对于 GCC 或 Clang,您可以使用AddressSanitizer:

$ export ASAN_OPTIONS=symbolize=1
$ gcc -Wall -Wextra -fsanitize=address a.c && ./a.out 
a.c: In function ‘main’:
a.c:6:5: warning: value computed is not used [-Wunused-value]
    6 |     *pnum++;
      |     ^~~~~~~
=================================================================
==2498121==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff15ae3e74 at pc 0x55d593978366 bp 0x7fff15ae3e30 sp 0x7fff15ae3e20
READ of size 4 at 0x7fff15ae3e74 thread T0
    #0 0x55d593978365 in main (/tmp/stackoverflow/a.out+0x1365)
    #1 0x7f525a1380b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
    #2 0x55d59397818d in _start (/tmp/stackoverflow/a.out+0x118d)

Address 0x7fff15ae3e74 is located in stack of thread T0 at offset 36 in frame
    #0 0x55d593978258 in main (/tmp/stackoverflow/a.out+0x1258)

  This frame has 1 object(s):
    [32, 36) 'num1' (line 3) <== Memory access at offset 36 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/tmp/stackoverflow/a.out+0x1365) in main
Shadow bytes around the buggy address:
  0x100062b54770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100062b54780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100062b54790: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100062b547a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100062b547b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100062b547c0: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1[04]f3
  0x100062b547d0: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100062b547e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100062b547f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100062b54800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100062b54810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==2498121==ABORTING

此跟踪告诉您:

  • 局部变量 ( stack-buffer-overflow) 中存在缓冲区溢出。
  • 溢出访问是尝试读取 4 个字节 ( READ of size 4)。
  • 有关问题地址 ( [32, 36) 'num1') 的更多信息。您可以看到程序在num1.
  • 有问题的指令 ( #0 0x55d593978365)的地址。您可以在调试器中设置断点以检查程序可能在做什么。

在大多数平台上,给定您的程序,堆栈num1上的变量是一个变量,而末尾是堆栈上前一个变量num1的地址。这可以是任何东西,具体取决于您的编译器如何访问内存的细节。这可能是许多事情之一pnum,如果pnumnum1碰巧在您的平台上具有相同的大小(这在 32 位平台上通常是这种情况)并且编译器决定将pnum之前num1放在堆栈上(这在很大程度上取决于编译器、优化级别和程序的细节)。所以你的程序打印 : 的地址是合理的,pnum不是因为*pnum不知何故没有调用解引用运算符,而是因为你的程序已经pnum 指向自己。


以上是在C中递增指针地址和值的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>