const 它究竟做了什么?

在C++中,为了定义常量,大部分人都不是继承C的特征去写宏定义,而是使用常量定义const。并且const也遍布了函数的返回值声明和参数声明,以确保函数的返回值或者参数不被使用的时候修改。那么事实上,const究竟做了什么呢?

那么这样的话我们就分为三种不同的使用分析分析const:

. const 常量定义的使用。先看一段代码:

#include <stdio.h>
int main(){
      const int a = 100;
      int * b =const_cast< int*>(&a);
      *b = 1;
      int& c = const_cast<int&>(a);
      c = 2;
        
      printf("a = %d, *b = %d, c = %d\n", a, *b, c); 
      printf("&a = %p, b = %p, &c = %p\n", &a, b, &c);
                                                                                                                                                                                                                   
      return 0;
  }

上面的代码输出是什么?又是为什么?先从输出说起:

a= 100 , *b = 2 , c = 2 
a= 0x7ffca83ec0e4 , *b = 0x7ffca83ec0e4 , c = 0x7ffca83ec0e4
看到这里,估计有人开始迷糊了。有以下几个问题:

1. a和c的地址与b所指向的地址都相同,为什么a输出的是100,但*b和c输出的都是2?

2. a作为常量,修改它的值是否能够成功修改?

对于第二个问题,或许从很多资料上来说,都是未定义操作。对于未定义操作的理解,可以单纯的理解为:你操作你的,我不保证正确性。由于是使用了const_cast操作,将a的const属性移除,从而修改了值。虽然这里可能也确实修改了a的值,但确实无法保证原有的a的正确性了呢。但是到底有没有真正的修改到a的值呢,这个问题比较容易验证,那么就简单的跟踪一下就可以看到:

哇唔,在这里修改之前*b的值也确实和a相同是100,因为也毕竟是指向a的地址啊。但当修改了*b的值之后,输出a的时候,也确实被修改了。那也就证明了通过以上的方式实际上是真正的修改a的值呢。那么,第一个问题就越发的奇怪了,我明明已经看到a的值被修改了呢,但这是几个意思呢?我们先暂且来个自我的思考分析:既然在输出a的值的时候确实没有修改,那么是不是在编译的时候将直接对于a的调用替换成了常数呢?const常量是在内存中分配了地址空间,因此应该不会像宏一样在预编译的时候被替换掉吧。为了验证这个问题,在Linux下使用g++ -E 就可以查看预编译的结果。实际如下(由于预编译的结果还是比较庞大,我就只选择main部分):

# 3 "test_const.cc" 
int main(){
      const int a = 100;
      int * b =const_cast< int*>(&a);
      *b = 1;
      int& c = const_cast<int&>(a);
      c = 2;
  
      printf("a= %d , *b = %d , c = %d \n" , a , *b , c);
      printf("a= %p , *b = %p , c = %p \n", &a, b, &c);
  # 22 "test_const.cc"
      return 0;
  }     

从上面的结果看,和源代码几乎没有区别。那么汇编后的结果呢?在Linux下可以使用g++ -S输出汇编后的代码:

    .file   "test_const.cc"
      .section    .rodata
  .LC0:
      .string "a= %d , *b = %d , c = %d \n"
  .LC1:
      .string "a= %p , *b = %p , c = %p \n"                                                                                                                                                                        
      .text
      .globl  main
      .type   main, @function
  main:
  .LFB0:
      .cfi_startproc
      pushq   %rbp
      .cfi_def_cfa_offset 16
      .cfi_offset 6, -16
      movq    %rsp, %rbp
      .cfi_def_cfa_register 6
      subq    $32, %rsp
      movq    %fs:40, %rax
      movq    %rax, -8(%rbp)
      xorl    %eax, %eax
      movl    $100, -28(%rbp)
      leaq    -28(%rbp), %rax
      movq    %rax, -24(%rbp)
      movq    -24(%rbp), %rax
      movl    $1, (%rax)
      leaq    -28(%rbp), %rax
      movq    %rax, -16(%rbp)
      movq    -16(%rbp), %rax
      movl    $2, (%rax)
      movq    -16(%rbp), %rax
      movl    (%rax), %edx
      movq    -24(%rbp), %rax
      movl    (%rax), %eax
      movl    %edx, %ecx
      movl    %eax, %edx
      movl    $100, %esi
      movl    $.LC0, %edi
      movl    $0, %eax
      call    printf
      movq    -16(%rbp), %rcx
      movq    -24(%rbp), %rdx
      leaq    -28(%rbp), %rax
      movq    %rax, %rsi
      movl    $.LC1, %edi
      movl    $0, %eax
      movl    $0, %eax
      call    printf
      movl    $0, %eax
      movq    -8(%rbp), %rsi
      xorq    %fs:40, %rsi                                                                                                                                                                                         
      je  .L3
      call    __stack_chk_fail
  .L3:
      leave
      .cfi_def_cfa 7, 8
      ret
      .cfi_endproc
  .LFE0:
      .size   main, .-main
      .ident  "GCC: (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010"
      .section    .note.GNU-stack,"",@progbits

这看的时候都想哭了,贴出来一堆的汇编代码,估计有很多人都不愿意看了。但这里有重要信息啊,如下:


从上述圈出的地方,可以看到原来常量在汇编成汇编代码的时候被直接替换了啊,怪不得在输出的时候是原值呢。终于理解为什么是这种输出了。但似乎问题没有这么简答的就结束呢,如果把常量作为普通函数的参数传递会是怎么样的呢?还是会像这个输出的时候直接向栈中压入常量值吗?带着这个问题,我又写了下面的代码做尝试:

#include <stdio.h>                                                                                                                                                                                               
  
  int fun_1(const int x){ 
      return x + 1;
  }
  
  int fun_2(int x){ 
      return x + 2;
  }
  
  int fun_3(const int &x){
      return x + 3;
  }
  
  int fun_4(int &x){
      return x + 4;
  }
  
  int main()
  {
      const int a = 100;
      int& c = const_cast<int&>(a);
      c = 3;
. 
      printf("fun_1:%d, fun_2:%d, fun_3:%d, fun_4:%d\n", fun_1(a), fun_2(a), fun_3(a), fun_4(const_cast<int&>(a)));
      return 0;
  }
这个问题的输出是什么?
fun_1:101, fun_2:102, fun_3:103, fun_4:104
这个结果说明了一个问题,在常数作为参数的情况下,传值和传引用在汇编的时候处理方式是不同的,如果是传值,则无论参数是const或者非const,都会在汇编期决定了传入参数的值。传引用的话就会按取当前内存中的值作为参数传递。这当然也是因为引用是对应变量的内存的别名的缘故吧。

二、const 作为参数的定义

代码就有使用上述的代码吧,有const参数传递的。直接看看汇编后的结果看看:

被红框圈住的是fun_2的汇编代码,没有被圈的是fun_1的汇编代码,仔细对照一下,发现啥变化都没有。之前还一直想过一个问题,const常量和非const变量,究竟在代码里是怎么标记的,从这里看来实际是没有任何标记的,其检查是否有const常量当作非const变量使用应该是由编译器检查。对于此假设,可以作为以后有空的时候的一个验证。


三、const作为函数返回值的声明

基于作为变量的分析,我有了这样一个猜想,在实际的函数定义中,返回非const和const是完全没有变化的。带着这个猜想,我写了以下三个函数声明,代码做分析测试:

  const int sum_1(int x, int y){ 
      return x + y;
  }
  
  int sum_2(int x, int y){ 
      return x + y;
  }
  
  int sum_3(int x, int y){ 
.     return x + y;
  }
汇编结果如下:

.globl  _Z5sum_1ii
      .type   _Z5sum_1ii, @function
  _Z5sum_1ii:
  .LFB0:
      .cfi_startproc
      pushq   %rbp
      .cfi_def_cfa_offset 16
      .cfi_offset 6, -16
      movq    %rsp, %rbp
      .cfi_def_cfa_register 6
      movl    %edi, -4(%rbp)
      movl    %esi, -8(%rbp)
      movl    -4(%rbp), %edx
      movl    -8(%rbp), %eax
      addl    %edx, %eax
      popq    %rbp
      .cfi_def_cfa 7, 8
      ret
      .cfi_endproc
  .LFE0:
      .size   _Z5sum_1ii, .-_Z5sum_1ii
      .globl  _Z5sum_2ii
      .type   _Z5sum_2ii, @function
  _Z5sum_2ii:
  .LFB1:
      .cfi_startproc
      pushq   %rbp
      .cfi_def_cfa_offset 16
      .cfi_offset 6, -16
      movq    %rsp, %rbp
      .cfi_def_cfa_register 6
      movl    %edi, -4(%rbp)
      movl    %esi, -8(%rbp)
      movl    -4(%rbp), %edx
      movl    -8(%rbp), %eax
      addl    %edx, %eax                                                                                                                                                                                           
      popq    %rbp
      .cfi_def_cfa 7, 8
      ret
      .cfi_endproc
  .LFE1:
      .size   _Z5sum_2ii, .-_Z5sum_2ii
      .globl  _Z5sum_3ii
      .type   _Z5sum_3ii, @function
  _Z5sum_3ii:                                                                                                                                                                                                      
  .LFB2:
      .cfi_startproc
      pushq   %rbp
      .cfi_def_cfa_offset 16
      .cfi_offset 6, -16
      movq    %rsp, %rbp
      .cfi_def_cfa_register 6
      movl    %edi, -4(%rbp)
      movl    %esi, -8(%rbp)
      movl    -4(%rbp), %edx
      movl    -8(%rbp), %eax
      addl    %edx, %eax
      popq    %rbp
      .cfi_def_cfa 7, 8
      ret
      .cfi_endproc
  .LFE2:
      .size   _Z5sum_3ii, .-_Z5sum_3ii
果然猜想是正确的,参数的限定也是有编译器去做检测的(这也是猜想)。那么,如果是以下调用的话会是怎么样的呢?

int main(){
      const int a = sum_1(2, 3); 
      const int b = sum_2(4, 5); 
      int c = sum_3(6, 7); 
      int d = sum_1(8, 9); 
      int e = sum_2(10, 11);
      const int f = sum_3(12, 13);
  
.     printf("a = %d, b = %d, c = %d, d = %d, e = %d, f = %d\n", a, b, c, d, e, f); 
      return 0;
  }
这个我就不做太多的赘述了,有兴趣的话可以自己做个简单的尝试分析分析。








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值