C++—函数、对象复用、零拷贝、标准库

函数

函数调用的压栈过程

#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 &para) =0;
};

//共享:子类化
class ConcreteFlyWeight:public  IFlyweight {
    
    
public:
    ConcreteFlyWeight(const std::string &state):IFlyweight(state){
    
    
    }
    void Func(const std::string &para) {
    
    
        std::cout << "ConcreteFlyWeight-->Func-->"<< para << " innerState:"<<innerState;
    }
};

//非共享:子类化
//不建议参入对象复用
class UnshareaConcreteFlyWeight:public  IFlyweight {
    
    
public:
    UnshareaConcreteFlyWeight(const std::string &state):IFlyweight(state){
    
    
    }
    void Func(const std::string &para) {
    
    
        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 分配器、本地化库、异常处理类、杂项支持库

猜你喜欢

转载自blog.csdn.net/qaaaaaaz/article/details/131264760
今日推荐