C++ 11 constexpr 常量表达式(关键字)深度剖析

constexpr 常量表达式是 C++ 11 语言标准里面提出的 “编译器语法功能”,其作用顾名思义为:常量表达式,尽量在编译阶段计算其结果。

例如:

#include <stdio.h>

int add(int x, int y) {
    return x + y;
}

void test() {
    int n = add(1, 2);
    printf("%d\n", n);
}

汇编: WINE-x86_32 + MSVC 2017(VC++ 2017) 

$SG5547 DB        '%d', 0aH, 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage

_x$ = 8                                       ; size = 4
_y$ = 12                                                ; size = 4
int add(int,int) PROC                                  ; add, COMDAT
        push    ebp
        mov     ebp, esp
        mov     eax, DWORD PTR _x$[ebp]
        add     eax, DWORD PTR _y$[ebp]
        pop     ebp
        ret     0
int add(int,int) ENDP                                  ; add

_n$ = -4                                                ; size = 4
void test(void) PROC                                 ; test
        push    ebp
        mov     ebp, esp
        push    ecx
        push    2
        push    1
        call    int add(int,int)                     ; add
        add     esp, 8
        mov     DWORD PTR _n$[ebp], eax
        mov     eax, DWORD PTR _n$[ebp]
        push    eax
        push    OFFSET $SG5547
        call    _printf
        add     esp, 8
        mov     esp, ebp
        pop     ebp
        ret     0
void test(void) ENDP                                 ; test

那么我们尝试为 add 函数增加 constexpr 关键字后再来观察其编译结果:

#include <stdio.h>

constexpr int add(int x, int y) {
    return x + y;
}

void test() {
    int n = add(1, 2);
    printf("%d\n", n);
}

注意需要打开 C/C++ 编译器优化: /Ox -O3

#include <stdio.h>

constexpr int add(int x, int y) {
    return x + y;
}

void test() {
    int n = add(1, 2);
    printf("%d\n", n);
}

汇编: WINE-x86_32 + MSVC 2017(VC++ 2017) 

$SG5547 DB        '%d', 0aH, 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage

_x$ = 8                                       ; size = 4
_y$ = 12                                                ; size = 4
int add(int,int) PROC                                  ; add, COMDAT
        mov     eax, DWORD PTR _x$[esp-4]
        add     eax, DWORD PTR _y$[esp-4]
        ret     0
int add(int,int) ENDP                                  ; add

void test(void) PROC                                 ; test
        push    3
        push    OFFSET $SG5547
        call    _printf
        add     esp, 8
        ret     0
void test(void) ENDP                                 ; test

人们从上述两个编译汇编结果中可以得出,C/C++ 编译器已经在编译期间把 add(1, 2) 的结果直接计算出来,并适用静态常量数值(3:结果)来代替原有代码需要调用 add 函数的情况。

如上述代码所示:

push 3,为调用 _printf 函数参数入栈,push offset $SG5547 为从DB数据中 fmt 字符串参数RVA地址,平衡函数调用参数栈大小:8字节(__cdecl  "C" 函数调用协议)

但需要注意:

如果在不打开 C/C++ 编译器优化的情况下,上面两个 C/C++ 代码编译后的结果其实是一致性的,即不会产生任何的静态表达式常量优化。

我会很好奇:

人们进一步尝试,add(1,2) 函数传递一个变量,那么 C/C++ 编译器优化后,最终编译输出的原生代码结果会是怎么样的?

#include <stdio.h>

constexpr int add(int x, int y) {
    return x + y;
}

void test(int left) {
    int n = add(left, 2);
    printf("%d\n", n);
}

汇编: WINE-x86_32 + MSVC 2017(VC++ 2017) 

$SG5548 DB        '%d', 0aH, 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage

_x$ = 8                                       ; size = 4
_y$ = 12                                                ; size = 4
int add(int,int) PROC                                  ; add, COMDAT
        mov     eax, DWORD PTR _x$[esp-4]
        add     eax, DWORD PTR _y$[esp-4]
        ret     0
int add(int,int) ENDP                                  ; add

_left$ = 8                                          ; size = 4
void test(int) PROC                                  ; test
        mov     eax, DWORD PTR _left$[esp-4]
        add     eax, 2
        push    eax
        push    OFFSET $SG5548
        call    _printf
        add     esp, 8
        ret     0
void test(int) ENDP                                  ; test

如上述汇编代码所示,当我们进行 C/C++ 编译器优化时,类似简单的 add 简单的运算表达式,可以被优化为 inline 的形式,但它并非明确的静态常量结果,而是需要进行动态计算。

add(left, 2),参数二为明确常量在被 C/C++ 编译器优化时,被明确为静态常量,其加值计算过程被内联(inline),所以可以清楚一个明确的点。

即:

constexpr 关键字声明的函数不需要 C/C++ 开发人员明确的增加 “inline” 关键字作为声明,最终 C/C++ 语言编译器,编译的结果是根据预期为 inline 来处理的。

人们在尝试更进一步,add(1,2) 函数传递全部变量时,那么 C/C++ 编译器优化后,最终编译输出的原生代码结果会是怎么样的?

#include <stdio.h>

constexpr int add(int x, int y) {
    return x + y;
}

void test(int x, int y) {
    int n = add(x, y);
    printf("%d\n", n);
}

汇编: WINE-x86_32 + MSVC 2017(VC++ 2017) 

$SG5549 DB        '%d', 0aH, 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage

_x$ = 8                                       ; size = 4
_y$ = 12                                                ; size = 4
int add(int,int) PROC                                  ; add, COMDAT
        mov     eax, DWORD PTR _x$[esp-4]
        add     eax, DWORD PTR _y$[esp-4]
        ret     0
int add(int,int) ENDP                                  ; add

_x$ = 8                                       ; size = 4
_y$ = 12                                                ; size = 4
void test(int,int) PROC                           ; test
        mov     eax, DWORD PTR _x$[esp-4]
        add     DWORD PTR _y$[esp-4], eax
        mov     DWORD PTR _x$[esp-4], OFFSET $SG5549
        jmp     _printf
void test(int,int) ENDP                           ; test

上述汇编为 C/C++ 编译器优化以后,其输出汇编代码的结果与上一个测试结果类似,但鉴于没有任何静态常量参数,所以整个计算过程被 inline(内联)后,其传入参数之间的运算为,动态载入计算堆栈其计算结果被推入计算堆栈后调用 _printf 函数大约输出结果到控制台屏幕上。

constexpr 关键字,C++ 编译器语言边界限制

第一条:

#include <stdio.h>

constexpr int add(int x, int y) {
    return x + y;
}

constexpr void test(int x, int y) {
    int n = add(x, y);
    printf("%d\n", n);
}

如上述代码似乎没有任何问题,但 constexpr void test(int x, int y) 函数无法被编译,因为它无法被 C/C++ 编译器理解为一条有效的 “常量表达式”。

原因为:

printf("%d\n", n),命令代码,删除它即可正确编译代码,从此处可以得出 constexpr 常量表达式函数,可以是递归调用执行的。

第二条:

struct people {
    int age;

    constexpr people() {
    
    }
}

上述代码结构体 “构造函数(constructor)” 上声明了 constexpr,似乎看上去没有问题,但这是错误的,构造函数是可变不确定的,当我们要构造一个结构及类时,构造函数内部实现无法确定且构造函数会改变结构本身的数据,并且从基础概念上构造函数与常量表达式两个不同的概念及产物,不可混为一谈,注意上述代码无法被 C/C++ 编译器编译通过。

第三条:

class people {
    int age;

public:
    constexpr ~people() {
        
    }
}

上述代码 “类析构函数” 上声明了 constexpr,似乎看上去没有问题,但问题与上述 “第二条” 存在的问题是相同的,析构函数是可变不确定的。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值