cpp学习-第二章-变量和基本类型

核心简记

  1. c++可以直接用的算数类型:bool(可赋予true或非0|false或0)表示出来为1或0;char(规定8位,GBK用俩个char表示);wchar-t宽字符;char16-t;char32-t用于unicode;short;int;long;long long;float;double;long double
  2. unsigned可用于char、int、short、long与long long;signed用于char。因为char的实现无符号还是有符号没定
  3. 类型转化:bool对外表示为0或1,可接受任意数字与truefalse,0为false。浮点赋予整形舍弃小数位。有符号与无符号表达式会转为无符号,负数的无符号会按补码计算无符号。不要使用和过度关注依赖编译环境的问题
  4. 字面值:0开头8进制,0x开头16进制。10进制字面值是带符号数,可用前后缀指定最小匹配。浮点默认double类型指数可用e标识。true与false是字面值。nullptr是指针字面值
  5. 俩个字符串字面值仅由空格换行分割时当成一个整体,可用转义符与泛化转义
  6. 字面值前缀字符与字串用:u(unicode16位)U(unicode32位)L(宽字符)u8(UTF-8仅用于字符串)。后缀整形:u/U(unsigned)l/L(long)ll/LL(long long)。后缀浮点型:f/F(float)l/L(long double)
  7. c++中,初始化与赋值是俩个完全不同的操作。{}列表初始化对转化要求更为严格。内置类型都应该初始化,类的初始化由类定义
  8. 为了支持分离式编译,c++将声明与定义区分开:声明使得名字所知,用于使用名字与类型。定义负责创建与名字关联的实体,用于分配空间与初始化。extern用于声明,如果初始化就成了定义。定义只有一次但可多次声明使用。
  9. C++为静态类型语言,即编译阶段检查类型,该检查判断出数据类型是否支持要执行的运算。适合复杂程序
  10. 定义应该放在使用附近,全局作用域下的名字使用::(前为空)访问。内可访问外层,内定义覆盖外层。
  11. 复合类型:基于其它类型定义的类型。见引用与指针
  12. 声明:基本数据类型 声明符(含名字与类型相关修饰)。int *p:int是基本类型,*是类型修饰符,类型修饰符是声明符的一部分。注意只有数据类型是作用于全语句的,类型修饰符只管p。因此一句可以定义不同类型变量。
  13. 处理类型可用auto与decltype。类型别名等-见下
  14. 基本数据类型:基本内置类型与自定义类型。基本内置类型:算数类型与空类型。算数类型:整形(字符与布尔在内)与浮点型
  15. 动态类型语言:在运行期间检查数据类型python、ruby、js等。静态类型语言:运行前编译时检查类型java、c++、c#等
  16. 默认初始化:内置类型函数外的为0,内部的不初始化。类决定对象的初始化方式

引用

引用是为对象起了另一个名字。初始化后永久绑定,不可更改。引用并非对象,只是另一个名字。

1
2
int i=1;
int &d=i;

d定义时必须初始化,类型除特殊外要求严格匹配。不可绑定字面值。
可以有指针的引用:

1
2
int *p;
int *&d=p;

离变量名最近的符号&对变量类型最直接的影响,因此d是引用,其余部分确定引用的类型。注意从右向左阅读修饰。

指针

指针本身是一个对象,允许赋值与拷贝,无需定义时赋值。

1
2
3
4
int *p;
int d;
p=&d;
*p=1;

引用不是对象,没有实际地址,不能定义指向引用的指针。除了特殊情况指针类型要求严格匹配。解指针引用得出所指对象。组成复合类型的符号与解引用符含义不同。
空指针初始可用nullptr,0。可以但不推荐NULL。应该初始化所有指针。暂不知道的就nullptr。
void*指针:特殊类型指针,可以存放任意类型对象的地址。
声明符中的修饰符没有个数限制,按逻辑关系解释即可,同时该声明明确了指针的具体类型。int **p;p是指向int的指针类型的指针。

const常量

1
2
3
4
5
6
7
const int i=1;
const int &d=i;
int i2=1;
const int &d2=i2;
const int &d3=123;
double i3=123.312;
const int &d4=i3;
  1. const关键字定义的对象必须初始化。之后不可被改变。
  2. 默认下,const只在文件内有效-编译单元,编译器将在编译过程中把用到const变量的都替换为对应值-但要分配空间的。想共享模块的const需要定义与声明都添加extern关键字
  3. 对常量的引用可以初始化为const、普通对象、任意能转化为引用类型的表达式。该const只是指明了该引用不可以使被引用的对象修改。也就是说该const是针对引用的可参与操作,引用本身就是不可被修改的
  4. 对于类型不一致可以的原因,是因为先用double初始化了一个const int &临时量,再用引用绑定这个临时量。也就是说const实际针对的是引用的类型

指向const的指针

1
2
3
4
const int i=1;
int i2=1;
const int *p=&i;
p=&i2;

想存放常量对象的地址,只能用指向常量的指针,但常量指针也可以存放普通对象的地址。常量指针仅要求不能通过该指针改变对象的值,也就是说规定的是该指针指向的类型是const。

const指针

1
2
3
int i=1;
int *const p=&i;
const int &d=i;//这个const是底层的,表示的是引用的对象是const类型的

同样右到左阅读,常量->指针->指向类型为int。该意思是指针本身为常量。指向类型的操作看指向类型的。
顶层const表示指针本身是个常量,底层const表示指针所指对象是常量。一般化,底层表示复合类型的,顶层表示本身。注意顶层底层是形容const的。
拷贝操作时,顶层不受影响。拷贝对象必须具有相同的底层const资格,或者俩个对象的数据类型可以转化。

常量表达式

1
2
3
const int i=f();//不是常量表达式
int i=1;//不是常量表达式
constexpr int i=xxxx;

常量表达式是指在编译时可以确定值的表达式,需要类型与初始值决定,字面值是。用常量exp初始化的const是。constexpr关键字由编译器验证变量的值是否是常量表达式,声明为该类型的变量一定是一个常量,而且必须用常量表达式初始化。当指针使用constexper时仅对指针有效-顶层且constexpr的指针和引用初始值限定很大。

处理类型

声明:基本数据类型 声明符(含名字与类型相关修饰)

类型别名

1
2
typedef int type1,*type2;
using type3=int;

typedef作为基本数据类型的一部分,声明符的名字作为类型别名。与声明一样,其它部分组成类型。使用using也可以
将别名用于常量类型不太一样:

1
2
3
typedef char *pstr;
const pstr p1=0;//p1是指向char的常量指针
const pstr *p2;//p2是指针,指向char的常量指针

不是替换后的指向常量的意义,也就是说理解不能展开,而是当成一个整体。从右到左读:基本类型为指针,之后的const是修饰该指针的。

auto

1
auto a=exp;

编译器通过初始值(必须有)判断变量的类型。auto作为声明语句中的基本数据类型,多条声明符时基本类型是一致的。
引用给auto实际上是使用引用的对象。auto会忽略顶层const,底层const会保留

1
2
3
4
5
const int i=1;
auto a=i;//a为int型
auto b=&i//a为指向整数常量的指针
//可以明确指出
const auto f=i;//i推演为int,f为const int

decltype

用于希望从表达式的类型推断出要定义变量的类型,但不用该表达式的值初始化变量。

1
decltype(exp) x=0;

编译器推断出exp的类型,并返回类型。该处理会返回真实的类型(包含const与引用)。一些表达式会向decltype返回引用类型,表示表达式结果对象可以做赋值语句的左值。

1
2
3
4
int *p;
decltype(*p) a;//a为int &引用类型-必须初始化,而不是int,因为表达式结果可以做左值
decltype(*p+0) b;//b为int型,因为不可做左值
decltype((b)) c;//c为int &,因为()为表达式,可以做左值,是引用。因此只要加()就是引用

自定义数据结构

1
2
3
4
5
6
//定义类
struct A{
int a=0;
};
//定义对象
A a;

可以直接用类名定义对象。c式也允许但不推荐,此外类的定义后可以加定义的对象,但不推荐。类内成员可以初始值,推荐。
按模块编程的话,一组.h与.cpp文件应该共同实现一个类,类名与俩文件名应该相同。.h是作为被共享出去的接口部分,因此类的定义、const应该定义在.h中。
保护符名:大写类名+_H

1
2
3
4
#include //展开
#define A //定义一个预处理变量
#ifdef A //定义A时为真,执行到#endif
#ifndef A //未定义A时为真,执行到#endif

预处理变量无视作用域,但是只在模块编译单元内生效。

反汇编

Android.mk

1
2
3
4
5
6
7
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := 2
LOCAL_SRC_FILES := 2.cpp
LOCAL_CFLAGS += -pie -fPIE -std=c++11
LOCAL_LDFLAGS += -pie -fPIE
include $(BUILD_EXECUTABLE)

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
struct A{
int a=0;
int b=1;
int c=2;
};
int main()
{
struct A sdad;
sdad.a=3;
float d1=3.4;
sdad.b=d1;
bool b1=123;
void *p1=nullptr;
const char *s1=u8"1234567";
//指针
int i1=12;
int *p2=&i1;
*p2=3;
//引用
int &qew=i1;
qew=123;
const int iqew=1;
int *p3=(int *)&iqew;
*p3=2;
int wqer=iqew;
return 0;
}

X86-64

指令简记

mov转义的大小看操作数和dword等类型

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
.text:00000000000005B0 sub_5B0 proc near ; DATA XREF: start+2E↑o
.text:00000000000005B0
.text:00000000000005B0 var_5C = dword ptr -5Ch
.text:00000000000005B0 var_58 = qword ptr -58h
.text:00000000000005B0 var_50 = qword ptr -50h
.text:00000000000005B0 var_48 = qword ptr -48h
.text:00000000000005B0 var_40 = qword ptr -40h
.text:00000000000005B0 var_38 = qword ptr -38h
.text:00000000000005B0 var_29 = byte ptr -29h
.text:00000000000005B0 var_28 = dword ptr -28h
.text:00000000000005B0 var_24 = dword ptr -24h
.text:00000000000005B0 var_20 = dword ptr -20h
.text:00000000000005B0 var_1C = dword ptr -1Ch
.text:00000000000005B0 var_18 = dword ptr -18h
.text:00000000000005B0 var_14 = dword ptr -14h
.text:00000000000005B0 var_8 = qword ptr -8//用于栈保护的8字节
.text:00000000000005B0
.text:00000000000005B0 ; __unwind {
//栈帧分配了60H
.text:00000000000005B0 push rbp
.text:00000000000005B1 mov rbp, rsp
.text:00000000000005B4 sub rsp, 60h
//栈保护
.text:00000000000005B8 mov rax, fs:28h
.text:00000000000005C1 mov [rbp+var_8], rax
.text:00000000000005C5 mov [rbp+var_24], 0
//rdi应该是this,这个结构体是分配在栈中的18 14 10 abc
.text:00000000000005CC lea rdi, [rbp+var_18]
.text:00000000000005D0 call sub_670
//sdad.a=3
.text:00000000000005D5 mov [rbp+var_18], 3
// float d1=3.4;
.text:00000000000005DC mov [rbp+var_28], 4059999Ah
// sdad.b=d1;
.text:00000000000005E3 movss xmm0, [rbp+var_28]
.text:00000000000005E8 cvttss2si ecx, xmm0
.text:00000000000005EC mov [rbp+var_14], ecx
// bool b1=123;
.text:00000000000005EF mov [rbp+var_29], 1
// void *p1=nullptr;
.text:00000000000005F3 mov [rbp+var_38], 0
// const char *s1=u8"1234567";
.text:00000000000005FB lea rax, a1234567 ; "1234567"
.text:0000000000000602 mov [rbp+var_40], rax
//int i1=12;
.text:0000000000000606 mov [rbp+var_1C], 0Ch
//int *p2=&i1;
.text:000000000000060D lea rax, [rbp+var_1C]
.text:0000000000000611 mov [rbp+var_48], rax
//*p2=3;
.text:0000000000000615 mov rdi, [rbp+var_48]
.text:0000000000000619 mov dword ptr [rdi], 3
// int &qew=i1;注意此时rax是i1的地址
.text:000000000000061F mov [rbp+var_50], rax
// qew=123;
.text:0000000000000623 mov rax, [rbp+var_50]
.text:0000000000000627 mov dword ptr [rax], 7Bh ; '{'
// const int iqew=1;
.text:000000000000062D mov [rbp+var_20], 1
//int *p3=(int *)&iqew;
.text:0000000000000634 lea rax, [rbp+var_20]
.text:0000000000000638 mov [rbp+var_58], rax
// *p3=2;
.text:000000000000063C mov rax, [rbp+var_58]
.text:0000000000000640 mov dword ptr [rax], 2
//int wqer=iqew;
.text:0000000000000646 mov [rbp+var_5C], 1
//栈保护
.text:000000000000064D mov rax, fs:28h
.text:0000000000000656 mov rdi, [rbp+var_8]
.text:000000000000065A cmp rax, rdi
.text:000000000000065D jnz loc_66B
//返回0 释放栈空间
.text:0000000000000663 xor eax, eax
.text:0000000000000665 add rsp, 60h
.text:0000000000000669 pop rbp
.text:000000000000066A retn
.text:000000000000066B ; ---------------------------------------------------------------------------
.text:000000000000066B
.text:000000000000066B loc_66B: ; CODE XREF: sub_5B0+AD↑j
.text:000000000000066B call ___stack_chk_fail
.text:000000000000066B ; } // starts at 5B0
.text:000000000000066B sub_5B0 endp
.text:0000000000000670 sub_670 proc near ; CODE XREF: sub_5B0+20↑p
.text:0000000000000670
.text:0000000000000670 var_8 = qword ptr -8
.text:0000000000000670
.text:0000000000000670 ; __unwind {
.text:0000000000000670 push rbp
.text:0000000000000671 mov rbp, rsp
//保存this到栈
.text:0000000000000674 mov [rbp+var_8], rdi
.text:0000000000000678 mov rdi, [rbp+var_8]
//为abc赋值
.text:000000000000067C mov dword ptr [rdi], 0
.text:0000000000000682 mov dword ptr [rdi+4], 1
.text:0000000000000689 mov dword ptr [rdi+8], 2
.text:0000000000000690 pop rbp
.text:0000000000000691 retn
.text:0000000000000691 ; } // starts at 670
.text:0000000000000691 sub_670 endp
.text:0000000000000691
.text:0000000000000691 _text ends

注意:

  1. 补码的意义是0减这个数的绝对值,x+x(反)+1=0.所以推得常用补码计算式,其表达的意义是为了计算方便。以0为对称符合负数的自然表达(用了00x作为对称),将二进制串用高位一分为二(越过中间将错误),因此使二进制计算结果正确。
  2. 浮点数有其专用的指令与寄存器进行计算。浮点数表示方式为先将10进制实数化为二进制实数(这一步造成了精度损失)再将二进制实数表示为IEEE规定的科学计数法样式-符号、指数、尾数。
  3. 布尔型由编译器存为1或0。占一个字节。
  4. 地址只能取一次(&),指针的类型只有编译器知道,汇编层面都是相同大小(8字节地址线长度)存地址。只能看操作区分用途
  5. 引用实质就是指针…只是编译器封装了指针操作而已..汇编层面的指针与引用完全没区别。引用是占内存的。都是间接寻址。
  6. 字面常量存储于机器码与数据段中,由系统保护机制防改。const是分配内存的,只是编译期间放改,用指针是可以改的,但是即使分配了内存,编译器依旧是对其在编译时进行了常量替换。
  7. 分析:地址->权限->属性->内存布局->数据操作

ARM64-v8

指令简记

WZR:32位0寄存器
XZR:64位0寄存器

SIMD全称Single Instruction Multiple Data,单指令多数据流,能够复制多个操作数,并把它们打包在大型寄存器的一组指令集。
Neon是适用于ARM Cortex-A系列处理器的一种128位SIMD(Single Instruction, Multiple Data,单指令、多数据)扩展结构。
64位下NEON寄存器:
包括:
32个B寄存器(B0~B31),8bit
32个H寄存器(H0~H31),半字 16bit
32个S寄存器(S0~S31) ,单字 32bit
32个D寄存器(D0~D31),双字 64bit
32个Q寄存器(V0~V31),四字 128bit

FCVTZS:浮点转换为有符号定点,向零舍入。

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
text:0000000000000798 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000000798 EXPORT main
.text:0000000000000798 main ; DATA XREF: LOAD:0000000000000458↑o
.text:0000000000000798 ; .got:main_ptr↓o
.text:0000000000000798
.text:0000000000000798 var_5C = -0x5C
.text:0000000000000798 var_58 = -0x58
.text:0000000000000798 var_50 = -0x50
.text:0000000000000798 var_48 = -0x48
.text:0000000000000798 var_40 = -0x40
.text:0000000000000798 var_38 = -0x38
.text:0000000000000798 var_2C = -0x2C
.text:0000000000000798 var_28 = -0x28
.text:0000000000000798 var_24 = -0x24
.text:0000000000000798 var_20 = -0x20
.text:0000000000000798 var_1C = -0x1C
.text:0000000000000798 var_18 = -0x18
.text:0000000000000798 var_14 = -0x14
.text:0000000000000798 var_8 = -8
.text:0000000000000798 var_s0 = 0
.text:0000000000000798
.text:0000000000000798 ; __unwind {
//减sp分配空间
.text:0000000000000798 SUB SP, SP, #0x70
//传栈帧与返回地址到栈里高地址
.text:000000000000079C STP X29, X30, [SP,#0x60+var_s0]
//x29新栈帧
.text:00000000000007A0 ADD X29, SP, #0x60
// X0是this,abc占栈中 -18 -14 -10
.text:00000000000007A4 SUB X0, X29, #-var_18
// v9 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
栈保护
.text:00000000000007A8 MRS X8, #3, c13, c0, #2
.text:00000000000007AC LDR X8, [X8,#0x28]
.text:00000000000007B0 STUR X8, [X29,#var_8]
.text:00000000000007B4 STUR WZR, [X29,#var_24]
//结构体初始化
.text:00000000000007B8 BL sub_86C
.text:00000000000007BC MOV W9, #1
.text:00000000000007C0 MOV W10, #2
.text:00000000000007C4 SUB X8, X29, #-var_20
.text:00000000000007C8 MOV W11, #0x7B
.text:00000000000007CC SUB X0, X29, #-var_1C
.text:00000000000007D0 MOV W12, #3
.text:00000000000007D4 MOV W13, #0xC
.text:00000000000007D8 ADRP X30, #a1234567@PAGE ; "1234567"
.text:00000000000007DC ADD X30, X30, #a1234567@PAGEOFF ; "1234567"
.text:00000000000007E0 MOV X14, XZR
.text:00000000000007E4 MOV W15, #1
.text:00000000000007E8 ADRP X16, #dword_894@PAGE
.text:00000000000007EC LDR S0, [X16,#dword_894@PAGEOFF]
//sdad.a=3;
.text:00000000000007F0 STUR W12, [X29,#var_18]
//float d1=3.4;
.text:00000000000007F4 STUR S0, [X29,#var_28]
//取整 sdad.b=d1;
.text:00000000000007F8 LDUR S0, [X29,#var_28]
.text:00000000000007FC FCVTZS W17, S0
.text:0000000000000800 STUR W17, [X29,#var_14]
// bool b1=123; W15=1 还是由编译器给1了 这些都应该是语义分析中间代码处理好的,所以后端不变
.text:0000000000000804 STURB W15, [X29,#var_2C]
//void *p1=nullptr;
.text:0000000000000808 STR X14, [SP,#0x60+var_38]
// const char *s1=u8"1234567";
.text:000000000000080C STR X30, [SP,#0x60+var_40]
//int i1=12;
.text:0000000000000810 STUR W13, [X29,#var_1C]
// X0=X29+var_1C 是i1的地址 var_48是p2
// int *p2=&i1;
.text:0000000000000814 STR X0, [SP,#0x60+var_48]
// *p2=3;
.text:0000000000000818 LDR X14, [SP,#0x60+var_48]
.text:000000000000081C STR W12, [X14]
// X0还是i1的地址 int &qew=i1;
.text:0000000000000820 STR X0, [SP,#0x60+var_50]
// qew=123;
.text:0000000000000824 LDR X14, [SP,#0x60+var_50]
.text:0000000000000828 STR W11, [X14]
// const int iqew=1;
.text:000000000000082C STUR W9, [X29,#var_20]
// X8=X29+var_20 是iqew的地址 int *p3=(int *)&iqew;
.text:0000000000000830 STR X8, [SP,#0x60+var_58]
//*p3=2;
.text:0000000000000834 LDR X8, [SP,#0x60+var_58]
.text:0000000000000838 STR W10, [X8]
// int wqer=iqew; 还是1
.text:000000000000083C STR W9, [SP,#0x60+var_5C]
// if ( *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40) == v9 )
栈保护
.text:0000000000000840 MRS X8, #3, c13, c0, #2
.text:0000000000000844 LDR X8, [X8,#0x28]
.text:0000000000000848 LDUR X14, [X29,#var_8]
.text:000000000000084C CMP X8, X14
.text:0000000000000850 B.NE loc_868
//返回0 放于W0
.text:0000000000000854 MOV W8, WZR
.text:0000000000000858 MOV W0, W8
//恢复栈帧
.text:000000000000085C LDP X29, X30, [SP,#0x60+var_s0]
.text:0000000000000860 ADD SP, SP, #0x70
.text:0000000000000864 RET
.text:0000000000000868 ; ---------------------------------------------------------------------------
.text:0000000000000868
.text:0000000000000868 loc_868 ; CODE XREF: main+B8↑j
.text:0000000000000868 BL .__stack_chk_fail
.text:0000000000000868 ; } // starts at 798
.text:0000000000000868 ; End of function main
.text:0000000000000868
.text:000000000000086C
.text:000000000000086C ; =============== S U B R O U T I N E =======================================
.text:000000000000086C
.text:000000000000086C
.text:000000000000086C sub_86C ; CODE XREF: main+20↑p
.text:000000000000086C
.text:000000000000086C var_8 = -8
.text:000000000000086C
.text:000000000000086C ; __unwind {
.text:000000000000086C SUB SP, SP, #0x10
.text:0000000000000870 MOV W8, #2
.text:0000000000000874 MOV W9, #1
//X0是this,放入栈高处
.text:0000000000000878 STR X0, [SP,#0x10+var_8]
.text:000000000000087C LDR X0, [SP,#0x10+var_8]
//初始化abc
.text:0000000000000880 STR WZR, [X0]
.text:0000000000000884 STR W9, [X0,#4]
.text:0000000000000888 STR W8, [X0,#8]
//恢复栈空间
.text:000000000000088C ADD SP, SP, #0x10
.text:0000000000000890 RET
.text:0000000000000890 ; } // starts at 86C
.text:0000000000000890 ; End of function sub_86C
.text:0000000000000890
.text:0000000000000890 ; .text ends

可见ARM的寄存器真的多….用的非常任性,先把值都放在寄存器再去赋予。c++的语法表示与x86基本相同..不过现代cpu针对计算有好多优化操作。