函数
函数调用的压栈过程
#include <iostream>
using namespace std;
int f(int n)
{
cout << n << endl;
return n;
}
void func(int param1, int param2)
{
int var1 = param1;
int var2 = param2;
printf("var1=%d,var2=%d", f(var1), f(var2));//如果将printf换为cout进行输出,输出结果则刚好相反
}
int main(int argc, char* argv[])
{
func(1, 2);
return 0;
}
//输出结果
//2
//1
//var1=1,var2=2
调用过程
- 从占空间分配存储空间
- 从实参的存储空间复制值到形参栈空间
- 进行运算
注意
- 形参在函数未调用之前都是没有分配存储空间的,在函数调用结束之后,形参弹出栈空间,清除形参空间。
- 数组作为参数的函数调用方式是地址传递,形参和实参都指向相同的内存空间,调用完成后,形参指针被销毁,但是所指向的内存空间依然存在,不能也不会被销毁。
- 当函数有多个返回值的时候,不能用普通的 return 的方式实现,需要通过传回地址的形式进行,即地址/指针传递。
形参与实参的区别
- 形参只有在被调用的时候才分配单元,调用结束的时候就释放单元了。
- 无论实参是什么类型的量,在进行函数调用的时候,都必须预先赋值,会产生一个临时变量。
- 实参和形参在数量、类型、顺序上应该严格一致。
C++中将临时变量作为返回值时的处理过程
函数退出的时候,临时变量出栈,临时变量被销毁,临时变量占用的内存没有被清空,但是可以被分配给其他变量,所以在函数退出的时候,这个内存已经被修改了,对于临时变量来说已经是没有意义的值了。
C语言里规定:16bit程序中,返回值保存在ax寄存器中,32bit程序中,返回值保持在eax寄存器中,如果是64bit返回值,edx寄存器保存高32bit,eax寄存器保存低32bit
由此可见,函数调用结束后,返回值被临时存储到寄存器中,并没有放到堆或栈中,也就是说与内存没有关系了。当退出函数的时候,临时变量可能被销毁,但是返回值却被放到寄存器中与临时变量的生命周期没有关系。
如果我们需要返回值,一般使用赋值语句就可以了。
方法调用的原理
机器用栈来传递过程参数、存储返回信息,保存寄存器用于以后恢复,以及本地存储。而为单个过程分配的那部分成为帧栈;帧栈可以认为是程序栈的一段,它有两个端点,一个标识起始地址,一个标识结束地址。两个指针结束地址指针esp,开始地址指针ebp。
过程实现
- 备份原来的帧指针,调整当前的栈指针到栈指针位置
- 建立起来的栈帧就是为被调用者准备的,当被调用者使用栈帧的时候,需要给临时变量分配预留内存。
- 使用建立好的栈帧,比如读取和写入
- 回复被调用者寄存器当中的值,这一过程其实是从栈帧中将备份的值再恢复到寄存器,不过此时这些值可能已经不在栈顶了
- 释放被调用者的栈帧,释放就意味着栈指针加大。而具体的做法一般是直接将栈指针指向帧指针
- 恢复调用者的栈帧,恢复就是调整栈帧两端,使得当前栈帧的区域又回到了原始的位置
- 弹出返回地址,跳出当前过程,继续执行调用者的代码
printf函数的实现原理
C/C++中,对于函数参数的扫描是从后向前的。C/C++的函数参数是通过压入堆栈的方式来给函数传参数的(堆栈是一种先进后出的数据结构),最先压入的参数最后出来,在计算机的内存中,数据有2块,一块是堆,一块是栈(函数参数及局部变量在这里),而栈是从内存的高地址向低地址生长的,控制生长的就是堆栈指针了,最先压入的参数是在最上面,就是说在所有参数的最后面,最后压入的参数在最下面,结构上看起来是第一个,所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方。
printf的第一个被找到的参数就是那个字符指针,就是被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可算出数据需要的堆栈指针的偏移量了。
cout和printf有什么区别
cout<<不是一个函数,而是std::ostream的全局对象。因为cout对各种数据类型进行重载,所以会自动识别数据类型。输出过程是==首先将输出字符放入缓冲区,然后输出到屏幕。
cout是有缓冲输出,flush是立即强迫缓冲输出,printf是行缓冲输出,不是无缓冲输出。
cout < < "abc " < <endl;
或cout < < "abc\n "; cout < <flush; 这两个才是一样的.
全缓冲
在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
行缓冲
在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。
不带缓冲
也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
程序在执行int main(int argc, char *argv[])时的内存结构
参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针char * 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称。
main函数的返回值
main函数返回值必须是int,这样返回值才能传递给程序及或者表示程序正常退出。
回调函数
- 发生某件事情的时候,系统或者其他函数将会自动调用你定义的一段函数。设置一个回调函数需要声明、定义、设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。
因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。
对象复用
减少对象的创建和销毁开销,提高性能和效率。通过对象复用,可以重复利用已经存在的对象,而不是频繁地创建或者销毁不再需要的对象。包括模板、宏、继承和多态、享元模式。
享元模式
Flyweight享元模式的定义:运用共享内存支持大量细粒度对象的复用,构建一套复用算法充分利用已经存在的对象。
参考享元模式标准写法
#include <stdio.h>
#include <string>
#include <iostream>
#include <map>
using namespace std;
//FlyWeight基类
class IFlyweight {
protected:
std::string innerState;//内部状态,保持稳定,初始化后,就不得更改
public:
IFlyweight(const std::string &state) {
innerState=state;
}
virtual void Func(const std::string ¶) =0;
};
//共享:子类化
class ConcreteFlyWeight:public IFlyweight {
public:
ConcreteFlyWeight(const std::string &state):IFlyweight(state){
}
void Func(const std::string ¶) {
std::cout << "ConcreteFlyWeight-->Func-->"<< para << " innerState:"<<innerState;
}
};
//非共享:子类化
//不建议参入对象复用
class UnshareaConcreteFlyWeight:public IFlyweight {
public:
UnshareaConcreteFlyWeight(const std::string &state):IFlyweight(state){
}
void Func(const std::string ¶) {
std::cout << "UnshareaConcreteFlyWeight-->Func-->" << para;
}
};
//享元工厂类,管理对象复用
//伪代码:该实例没有考虑多线程安全问题
class FlyWeightFactory{
private:
//共享池
//数据结构为:map
//算法:key-value
map<std::string, IFlyweight*> pool;
public:
//获取复用对象
IFlyweight* getFlyWeight(const std::string &key) {
map<std::string, IFlyweight*>::iterator item=pool.find(key );
if (item != pool.end()) {
//如果查询到,直接返回
return pool[key];
}else{
//创建新对象,保存到pool里,并返回
ConcreteFlyWeight *product=new ConcreteFlyWeight(key);
pool[key]=product;
return product;
}
}
//清理
void clear(){
pool.clear();
}
~FlyWeightFactory(){
};
};
int test7_1(int argc, const char * argv[]) {
//1:创建对象池,享元工厂类
FlyWeightFactory *factoryPool=new FlyWeightFactory();
//2:创建对象和复用对象
//新建
ConcreteFlyWeight *item1= static_cast<ConcreteFlyWeight *>( factoryPool->getFlyWeight("key1"));
item1->Func("item1");
//新建
IFlyweight *item2=static_cast<ConcreteFlyWeight *>( factoryPool->getFlyWeight("key2"));//新建
item2->Func("item2");
//复用,key1
IFlyweight *item3=static_cast<ConcreteFlyWeight *>( factoryPool->getFlyWeight("key1"));
item3->Func("item3");
//复用,key2
IFlyweight *item4=static_cast<ConcreteFlyWeight *>( factoryPool->getFlyWeight("key2"));
item4->Func("item4");
return 0;
}
零拷贝
避免CPU将一块存储拷贝到另外一块存储的技术,可以减少数据拷贝和共享总线操作的次数。
emplace_back
使用push_back()函数需要调用拷贝构造函数和转移构造函数,而使用emplace_back()插入的元素原地构造,不需要触发拷贝构造和转移构造。
#include <vector>
#include <string>
#include <iostream>
using namespace std;
struct Person
{
string name;
int age;
//初始构造函数
Person(string p_name, int p_age): name(std::move(p_name)), age(p_age)
{
cout << "I have been constructed" <<endl;
}
//拷贝构造函数
Person(const Person& other): name(std::move(other.name)), age(other.age)
{
cout << "I have been copy constructed" <<endl;
}
//转移构造函数
Person(Person&& other): name(std::move(other.name)), age(other.age)
{
cout << "I have been moved"<<endl;
}
};
int main()
{
vector<Person> e;
cout << "emplace_back:" <<endl;
e.emplace_back("Jane", 23); //不用构造类对象
vector<Person> p;
cout << "push_back:"<<endl;
p.push_back(Person("Mike",36));
return 0;
}
//输出结果:
//emplace_back:
//I have been constructed
//push_back:
//I have been constructed
//I am being moved.
mmap
避免了用户空间和内核空间的数据拷贝
sendfile
将文件直接发送到socket从而避免了用户空间和内核空间之间的数据拷贝
Gather Copy DMA零拷贝方式
由于该拷贝方式是由DMA完成,与系统无关,所以只要保证系统支持sendfile系统调用功能即可。
该方式中没有数据拷贝到socket buffer。取而代之的是只是将kernel buffer中的数据描述信息写到了socket buffer中。数据描述信息包含了两方面的信息:kernel buffer中数据的地址及偏移量
标准库
-
标准函数库: 这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自 C 语言。输入/输出 I/O、字符串和字符处理、数学、时间、日期和本地化、动态分配、其他、宽字符函数
-
面向对象类库: 这个库是类及其相关函数的集合。标准的 C++ I/O 类、String 类、数值类、STL 容器类、STL 算法、STL 函数对象、STL 迭代器、STL 分配器、本地化库、异常处理类、杂项支持库