精华内容
下载资源
问答
  • 不同名函数化同名函数
    千次阅读
    2021-03-24 15:24:28


    起因

    由于业务需要,我司使用了Mellanox某闭源C++程序,Mellanox推荐的定制化开发方法是:对其链接的动态库进行定制化开发,以添加额外的功能。

    在方案讨论阶段,发现很多同事对动态库/静态库所代表的的含义并不十分清楚,特别是当同名函数存在时,编译、链接、运行的结果是什么也没有明确的认识,故写下这篇文章。

    基本概念

    程序函数库可分为下面几种类型:

    1. 静态函数库(static libraries):在编译期间(compile-time)静态链接库会全部拷贝进编译对象中,一般以.a文件的存在
    2. 动态函数库(shared libraries):在程序启动的时候加载到程序中,它可以被不同的程序共享,一般以.so文件存在
      • 动态加载函数库(dynamically loaded libraries),在进程运行期间,使用dlfcn.h中的函数加载、调用、关闭动态库

    关于动态库和静态库的优缺点,相关文章很多,这里不再赘述

    同名函数测试

    使用两个.c文件test2.ctest2.c包含同名函数void test()

    // test1.c
    #include <stdio.h>
    
    void test() {
        printf("call from test1.c");
    }
    
    // test2.c
    #include <stdio.h>
    
    void test() {
        printf("call from test2.c");
    }
    

    含有main函数的文件main.c

    // main.c
    extern void test();
    int main() {
        test();
    }
    

    测试1:.o目标文件

    使用如下命令行,将test2.ctest2.c生成目标文件,并编译可执行文件

    gcc -c ./test1.c
    gcc -c ./test2.c
    gcc -o main ./test1.o ./test2.o ./main.c
    

    结果报错:

    ./test2.o: In function `test':
    test2.c:(.text+0x0): multiple definition of `test'
    ./test1.o:test1.c:(.text+0x0): first defined here
    collect2: error: ld returned 1 exit status
    

    可见,将包含同名函数的目标文件进行链接,如果其在同一个命名空间中,会报multiple definition错误。

    测试2:静态库

    使用如下命令行编译静态库libtest1.alibtest2.a

    g++ -c ./test1.c
    g++ -c ./test2.c
    ar crv libtest1.a test1.o
    ar crv libtest2.a test2.o
    

    接着我们链接编译:

    gcc -L. ./main.c -ltest1 -ltest2 -o main
    

    可以编译成功,无报错。执行结果如下

    $ LD_LIBRARY_PATH=. ./main
    call from test1.c
    

    有朋友会问:“为什么没有报错呢?我明明把包含同名函数的两个静态库链接进同一个可执行文件了。”

    为了探究为什么没有报错,我们增加ld选项-Wl,--verbose来看看链接时到底发生了什么。再执行编译,我们得到输出:

    ...
    
    attempt to open ./libtest1.so failed
    attempt to open ./libtest1.a succeeded
    (./libtest1.a)test1.o
    attempt to open ./libtest2.so failed
    attempt to open ./libtest2.a succeeded
    
    ...
    

    可以发现,最终的链接结果,输出的二进制文件只链接了libtest1.a背后的test1.o文件,而没有链接libtest2.a。编译器这么做的含义是:

    1. 编译器根据链接先后顺序,依次查找链接库。
    2. 首先查找libtest1.a,发现其有main函数需要的函数void test(),因此将其进行了链接。
    3. 再扫描到libtest2.a的时候,由于void test()已经被libtest1.a中的符号提供,因此不再链接。

    Stack Overflow中有个问题也谈到了这点。

    如果使用ld参数--whole-archive强行链接libtest1.alibtest2.a,我们会看到和测试1同样的报错:

    $ gcc -L. ./main.c -Wl,--whole-archive -ltest1 -ltest2 -Wl,--no-whole-archive -o main
    ./libtest2.a(test2.o): In function `test':
    test2.c:(.text+0x0): multiple definition of `test'
    ./libtest1.a(test1.o):test1.c:(.text+0x0): first defined here
    collect2: error: ld returned 1 exit status
    

    测试3:动态库

    使用如下命令行编译动态库libtest1.solibtest2.so并编译可执行文件。

    gcc -shared -fPIC -o libtest1.so test1.c
    gcc -shared -fPIC -o libtest2.so test2.c
    gcc -L. ./main.c -ltest1 -ltest2 -o main
    

    编译无报错,ldd检查,libtest1.solibtest2.so确实都链接进main可执行文件中。执行结果如下:

    $ LD_LIBRARY_PATH=. ./main
    call from test1.c
    

    可见,在动态链接时,不同的链接库可以有同名函数,不影响编译。这是由动态链接库的性质决定的,其只有在运行时才会动态加载,并且加载的顺序是由编译时链接的顺序决定的。这也就说符号会以第一个查找到的为准(Symbols are resolved on a first match basis)。

    我们也可以通过设置LD_PRELOAD提前将某动态库load进内存

    同名函数的应用

    有朋友会提出这样的疑问,上面虽然做了这么多实验,但多少有点语言律师的感觉,这些知识能改善我们日常生活吗?日常工作中能用的到吗?答案当然是能用得到。

    最简单的应用场景,比如某开源库中有个函数我不喜欢,我想写个自己的版本替换掉,那么完全可以利用上述的知识,将自己实现的某函数以动态或者静态的方式链接进可执行文件中,替换自己不喜欢的版本。

    工业上常见的应用有以下几种:

    1. 替换库:大名鼎鼎的tcmalloc就是以这种方式运行的。我们将tcmalloc链接进程序,只要tcmalloc库的查找顺序优先于libc,就可以替换原生的内存管理函数为tcmalloc版本。
    2. mock测试:陈硕在文章中详述了如何在C++单元测试中mock系统调用。其中的链接期垫片 (link seams)方法,就是利用libc一般情况下是动态链接的特性,在进程中mock系统调用。
    更多相关内容
  • 同名函数之间的关系

    千次阅读 2019-08-02 15:30:47
    在C++中同名函数存在如下三种关系: 函数重载/重定义:指的是在同作用域下,由函数参数列表(参数类型、参数个数、参数顺序)提供支持的多个同名函数同时存在的一种关系; int sum(int a,int b) { return a+b; } ...

    在C++中同名函数存在如下三种关系:

    • 函数重载/重定义:指的是在同作用域下,由函数参数列表(参数类型、参数个数、参数顺序)提供支持的多个同名函数同时存在的一种关系;
    int sum(int a,int b)
    {
    	return a+b;
    }
    
    int sum(int a,int b,int c)
    {
        return a+b+c;
    }
     
    double sum(double a,double b)
    {
    	return a+b;
    }
    
    
    float sum(float a,float b)
    {
    	return a+b;
    }
     
    注意一点:函数返回值并不对函数重载提供支持,因为在函数的调用点与函数匹配时只能推演函数名、函数参数列表,看不见返回值类型。另外常函数也是函数重载的一种形式。
    • 隐藏:在类的不同作用域下,准确的说是基类与派生类、父类与子类的关系下,子类中如果有和父类同名的函数,则继承之后的子类的同名函数完全会将该类的基类部分同名函数隐藏起来,简单来说就是基类的同名函数存在,但是派生类对象不可见的情况,派生类对象如果调用该同名函数,则调用的是派生类中的那个同名函数;当然类的成员变量也遵循这种隐藏关系;如果可以去调用基类中定义的同名函数的话,就要在函数名前加上基类作用域Base::函数名();
    class  A{
    public:
        A(int a):a(ma){}
    
          void show()
        {
           cout<<"a="<<a<<endl;
        }
    public:
         int ma;
    };
    
    
    class  B: public A{
    public:
        A(int a,int b):A(ma),b(mb){}
    
          void show()
        {
           cout<<"b="<<a<<endl;
        }
    public:
         int ma;
         int mb;
    };
    
    int main()
    {
      B  b(4,5);
      b.show();//5
      b.A::show();//4
      return 0;
    }
    
    
    
    • 覆盖/重写:此关系在函数同名基础上多一个条件-------------基类与派生类中同名函数要求参数相同且基类函数前必须有virtual关键字,函数的返回值也要相同。覆盖这一特性是由虚表提供支持的,一个类不管有多少虚函数,只生成一个虚函数指针,虚函数指针指向虚函数表,虚函数表中存放的是函数入口地址,该类的每个对象共用一个虚函数指针。

           在指针调用虚函数且对象完整时才发生多态,覆盖、重载属于方法的多态性,而对象的多态性体现在基类与派生类的转型:

                     向上转型,目的是限制对于子类对象特有属性的访问 Animal a = new Cat();

                     向下转型,目的是为了访问子类的特有属性 Cat c = (Cat)a;

    #include<iostream>
    using namespace std;
     
    class Base
    {
    public:
    	virtual void fun(){}  //成为虚函数的三个前提条件:依赖对象调用、对该函数可以取地址;比如说一般的成员方法和析构函数;
        int a;
    };
    
     class Derive:public Base
    {
    public:
        void fun()
        {
           cout<<"-----"<<endl;
        }
    int b;
    
    };
    void main()
    {
    	  Derive d;//生成一个派生类对象
              Base*pb=&d;//基类指针指向派生类对象
    }

    内存布局:

    • 纯虚函数:虚函数写成void  show()=0;这种形式的时候虚函数变成纯虚函数,所在的类成为抽象类,抽象类中纯虚函数在派生类中必须实现,否则派生类还是抽象类,不能生成对象,没有意义。抽象类不能实例化对象,但是允许基类的指针或者引用指向派生类的对象,此时应该将析构函数也写成虚函数,不然被指向的对象资源不能完全释放。
    • 虚继承:虚继承为的是解决访问变量或者成员方法不明确的问题;
    #include <iostream>
    using namespace std;
    class A
    {
    public:
    	int num;
    	A()
    	{
    		num=100;
    		cout<<"A"<<endl;
    	}
    	~A()
    	{
    		cout<<"~A"<<endl;
    	}
     
     
    };
     
    class B:virtual public A //虚继承
    {
    public: 
    	B()
    	{
    		cout<<"B"<<endl;
    	}
    	~B()
    	{
    		cout<<"~B"<<endl;
    	}
    };
     
    class C:virtual public A //虚继承 
    {
    public: 
    	C()
    	{
    		cout<<"C"<<endl;
    	}
    	~C()
    	{
    		cout<<"~C"<<endl;
    	}
    };
    class D:public B,public C
    {
    public: 
    	D()
    	{
    		cout<<"D"<<endl;
    	}
    	~D()
    	{
    		cout<<"~D"<<endl;
    	}
    };
    
    菱形继承明显的问题就是造成访问的不明确;
    
    int main()
    {
    	D d;
    	//cout<<d.num<<endl;//error:访问不明确;没加virtual前;
    	//第一种方案:清楚的表明作用域,以免出现歧义:
    	//cout<<d.B::num<<endl;
    	//cout<<d.C::num<<endl;
    	//第二种方案:加上virtual后,就没有歧义了,B和C都指向同一份A;
    	cout<<d.num<<endl;//加上virtual OK
    	system("pause");
    	return 0;
    }

     

    展开全文
  • js函数的定义及同名函数覆盖问题

    千次阅读 2019-09-18 19:59:05
    js函数的定义用函数声明语法定义用函数表达式定义使用Function的构造函数当分别用前两种方式定义同一个函数时 用函数声明语法定义 function add(num1,num2){ var sum=sum1+sum2; return sum; } console.log(add(1,...

    用函数声明语法定义

    function add(num1,num2){
        var sum = sum1 + sum2;
        return sum;
    }
    console.log(add(1,2));//3
    

    用函数表达式定义

    形式一

    var sum = function(num1,num2){
       var sum = sum1 + sum2;
       return sum;
    }
    console.log(sum(1,2));//3
    

    形式二

    var sum = function s(num1,num2){
       var sum = sum1 + sum2;
       return sum;
    }
    console.log(sum(1,2));//3
    

    形式二的作用等同于形式一,而且原函数名s就不代表函数了,等同于一个匿名函数。如果执行s(1,2)将会报错

    使用Function的构造函数

    var sum = new Function("num1","num2","num=num1+num2;return num");
    console.log(sum(1,2));//3
    

    当分别用前两种方式定义同一个函数时

    • 首先我们知道:JS函数没有重载,后面定义的同名函数会覆盖掉之前的函数。函数名是一个指针,值是一段字符串。
      定义的先后顺序会导致不同的结果,看下面两个例子
    1. 后用函数表达式定义
      结果符合我们的预期
    function fn1(){
       alert("Hello World!");
    }
    fn1();//Hello World!
    var fn1 = function(){
    	alert("Hello!");
    }
    fn1();//Hello!
    
    1. 先用函数表达式定义
      为什么第二次输出的还是Hello, 而不是Hello World!
      原有由下:解析器会首先读取函数声明,再执行代码;而函数表达式的函数则会在执行到的时候解析器才会去解析。所以JavaScript引擎会提升函数声明到顶端(即相当于先用了函数声明定义,再用了函数表达式定义),而变量声明只是变量的声明部分提升,赋值并不执行。
    var fn1 = function(){
        alert("Hello!");
    }
    fn1();//Hello!
    function fn1(){
        alert("Hello World!");
    }
    fn1();//Hello!
    
    展开全文
  • 函数名相同 作用域相同 函数参数不同 class Base { public: //这两个函数构成重载 void show() { cout << "Base::void show()" << endl; } void show(int i) { cout << "virtual ...

    1.重载

    • 函数名相同
    • 作用域相同
    • 函数参数不同
    class Base
    {
    public:
    	//这两个函数构成重载
    	void show()
    	{
    		cout << "Base::void show()" << endl;
    	}
    	void show(int i)
    	{
    		cout << "virtual void show(int i )" << endl;
    	}
    
    };
    
    这两个函数就构成了重载,在相同的作用域内,函数名相同,参数不同

    2.隐藏

    隐藏很简单,就是派生类中的函数会隐藏基类中同名的函数,无关其他

    class Base
    {
    public:
    	void show()
    	{
    		cout << "Base::void show()" << endl;
    	}
    };
    
    class Derive :public Base
    {
    	//隐藏父类中的void show函数
    public:
    	void show()
    	{
    		cout << "Derive::void show()" << endl;
    	}
    
    };

    隐藏发生后,父类还是调用父类的函数,子类还是调用子类的函数

    3.覆盖

    • 基类和派生类中的函数同名,同参数
    • 基类中的函数是虚函数
    • 派生类中的同名同参的函数覆盖了基类中同名同参的虚函数
    class Base
    {
    public:
    	//这两个函数构成重载
    	virtual void show()
    	{
    		cout << "Base::void show()" << endl;
    	}
    	void show(int i)
    	{
    		cout << "virtual void show(int i )" << endl;
    	}
    };
    
    class Derive :public Base
    {
    	//隐藏父类中的void show函数
    public:
    
    	void show()
    	{
    		cout << "Derive::void show()" << endl;
    	}
    };
    
    int main()
    {
    	Base *base = new Derive();
    	base->show();
    
    	return 0;
    }

    定义一个基类的指针指向子类的对象,调用show函数的时候派生类会把基类的show函数覆盖,所以调用的是派生类的

    面试点

    1.派生类继承了基类的什么?

    我们通过下面一段代码看一下

    #include<iostream>
    #include<string>
    
    
    class People
    {
    public:
    	People(std::string name, int age, bool sex)
    		:mname(name), mage(age), msex(sex){}
    	void eat()
    	{
    		std::cout << mname << " can eat !" << std::endl;
    	}
    	static int ma;
    	void Show()
    	{
    		std::cout << "hello world!" << std::endl;
    	}
    	typedef int Elem_Type;
    protected:
    	std::string mname;  //28
    	int mage;  // 4
    	bool msex; // 4
    
    };
    int People::ma = 10;   //静态的成员变量需要在类外初始化
    
    class Student : public People
    {
    public:
    	Student(std::string name, int age, bool sex, std::string id)
    		:mid(id), People(name, age, sex){}
    protected:
    	std::string mid;//28
    };
    int main()
    {
    	Student stu("zhangsan", 20, true, "001");
    	stu.eat();   
    	std::cout << sizeof(Student) << std::endl;
            std::cout << sizeof(People) << std::endl;
    	std::cout<<Student::ma<<std::endl;
    	stu.Show();
    	Student::Elem_Type a;
    	return 0;
    }
    
    我们在student中没有实现任何方法,但是我们从main函数调用发现,stu可以调用people中任意一个成员函数和成员变量,打印子类student的大小也是父类的大小加上自己的大小

     

    所以派生类继承了父类中除了构造函数和析构函数意外所有的成员。

    2.派生类的内存布局

    基类的布局优先于派生类

    3.派生类的析构方式和构造方式

    又上图可以看出,父类先构造,然后子类在构造;在析构的时候正好相反,子类先析构,父类在析构

    4.继承方式的权限

       1.子类无法访问父类中的private成员
       2.父类的protected成员可以被子类访问,但是不可以被外部访问
       3.private会被继承,但是无法访问
       4.class中默认的方式是private,继承默认也是private方式
       5.父类中成员通过相应的继承方式继承给子类后,这些成员对于子类来说,访问权限以继承的等级为最低级别。例如:父类中A为private权限,无论通过什么方式都不可以访问该变量

    5.类和类的关系

            1.组合   a part of  has_a
            2.继承   a kind of  is_a
            3.代理

     

     

     

     

    展开全文
  • 如果在不同的c文件中定义了同名的全局变量,则它们会占用相同的内存空间,而且编译链接时不会报错! 这可以参考全局变量的内存初始顺序 对于局部变量而言,内存分配的顺序和代码的顺序是一样的。全局变量就...
  • c++同名函数----重载、隐藏、覆盖

    千次阅读 多人点赞 2018-08-21 12:55:34
    函数的返回类型可以相同也可以相同。 仅仅返回类型不同足以成为函数的重载。 相同的范围(在同一个类中); virtual 关键字可有可无。 为什么需要函数重载? 试想如果没有函数重载机制,如在C中,你必须要...
  • 开始我没有细想,就说可能会出错吧,可是等我实验完了发现页面没有任何脚本错误提示,而且程序也运行了,只是对同名函数的调用执行了位置靠后的一个。  回头仔细一想,这个结果完全可以接受,因为脚本在页面里本身...
  • 简述C++类继承的关系,运用过程中涉及到的初始列表,类内部使用其他类等。
  • 在Python中居然可以定义两个同名通参数的函数一个意外的场景,我发现Python模块中是可以定义相同的名称和参数的函数的,虽然在eclipse中报错了,但是执行时没有问题的,这个是IDE的问题。其中的含义是因为第一个函数...
  • 背景:在开发图形挖矿软件时,需要调用非界面软件接口。思路1,编译ccminer及cpuminer成可执行文件,直接调用;...难点:由于两个动态库存在大量同名变量及函数,直接改函数名十分耗时。 解决方法:...
  • 函数是Python为了代码最大程度的重用和最小代码冗余而提供的基本程序结构,用于将相关功能打包并参数 Python中可以创建4种函数: 1)、全局函数:定义在模块中,每个模块都是一个全局作用域,因此,全局作用域...
  • Python类中有出现同名函数

    千次阅读 2018-08-14 23:00:24
    class demo(): def func(self): print("hello China") def func(self): print("hello world") if __name__ == '__main__': demo.func() ...这里可以理解为第二个的func()覆盖了第一个fu...
  • 一般函数同名:当某个函数func()在基类和派生类中都有定义时,派生类中的函数func()将修改从基类继承来的函数func(),如果非要从派生类中访问基类的函数func(),有两种方法:一、定义基类指针,让基类指针...
  • 先说下预解析的含义,在写js代码调用函数的时候,无论你是在调用位置的前面或者后面声明函数,都可以正常调用,原因是,JavaScript碰到script标签,会将var变量(注意是var)声明和函数声明(注意是声明)提升到当前...
  • 其实关于返回局部变量只是python的问题,凡是使用堆栈结构处理函数的语言都会有这样的问题,切记不要返回局部变量。因为当创建函数的堆栈撤销,所有对局部变量的修改都灰飞烟灭。来看我的小例子1 defhandle():2 ...
  • 文章目录1 子类定义父类同名成员2 子类定义父类同名函数3 小结 1 子类定义父类同名成员 子类中可以定义父类的同名成员 子类中的成员将隐藏父类中的同名成员 父类中的同名成员依然存在于子类中,需要用作用域分辨符...
  • C++ 实现 根据字符串 调用同名函数

    千次阅读 2012-08-24 18:35:03
    需求: 希望根据用户的输入调用同名函数。 因为想写各种 if else,所以就建立一个key为string,value为函数指针的map,根据string的值调用相应的函数。 以下代码在gcc 3.4.6下测试通过。 下面是代码...
  • C++类用三个特殊的成员函数:复制构造函数、赋值操作符和析构函数 来决定类对象之间的初始或赋值时发生什么。所谓的“复制控制”即通过这三个成员函数控制对象复制的过程。本篇文章将介绍复制构造函数。  复制...
  • 定义多个同名函数,但其参数的顺序或命名不同,在调用时,根据提供的参数进行调用,返回对应的值。 不同语言对比 C++: int square(int a) { return x * x; } float square(float b) { return x * x; } 在调用...
  • 在JavaScript中,当一般变量、函数函数参数重名时,它们的优先级为: 已初始的一般变量 &...},因为函数的优先级大于未初始的一般变量 console.log(a); var a = 1; function a() { var b = 0; } //结果.
  • c 处理同名函数

    千次阅读 2011-07-19 07:37:54
    放到结构体里面,动态加载嘛,程序初始的时候,加载一遍, 比如,ibm函数全部放到ibm_func, 但是,sun也有自己的实现,那么,放到另一个结构体里面, 当然,结构体只是为了管理方便,思路是用指针保存起来, 原文...
  • #include #include #include using namespace std; /**  * 定义人类: Person  * 数据成员: m_strName  * 成员函数: attack()  */ class Person { public:  Person()  {  cout  }  Per
  • Linux 动态库同名函数处理原则

    千次阅读 2014-08-04 16:23:07
    问:有一个主执行程序main,其中实现了函数foo(),同时调用动态库liba.so中的函数bar(),而动态库liba.so中也实现了foo()函数,那么在执行的时候如果在bar()中调用foo()会调用到哪一个?在main()中调用呢?  ...
  • 这里做的比较简单,只是声明一个类,类初始的时候读取配置文件,根据配置列表加载特定目录下的模块下的函数函数和模块同名,将此函数动态加载为类的成员函数。 代码如下所示: class WinBAS(Bas): def __init_...
  • 说明:学过java的同学都知道,如果java代码中存在这样的两个函数:这两个函数函数名相同但参数不同,那么这是两个不同的函数,也可以称之为函数的重载。那么,如果python中如果出现这样的两个函数,情况会与在Java...
  • 三角函数 公式【和差化积、积和差】最简记忆口诀
  • 如果以一个衍生类指针指向一个基础类对象,必须先做强制转型动作(explicit cast),这种做法很危险,也符合语法习惯,在程序设计上也会给程序员带来困扰。(一般不会这么去定义) 如果基础类和衍生类定义了相同...
  • c++ 构造函数初始过程

    千次阅读 2018-09-14 11:26:52
    每个类都会为它的对象定义初始的方式,用一个或几个特殊的函数去控制对象的初始,我们把这些特殊的函数叫做 构造函数。... 答: 构造函数函数名与类名同名,没有返回值类型,有一个参数列表(...
  • C语言为什么支持函数重载

    千次阅读 2019-04-30 17:55:25
    函数重载 在我们中国的文化中,一个词可能是有多种含义,在这种情况下就可以通过上下文来判断这个词到底是什么意思,在这里我们就可以理解为这个词被重载了。 例:又是晴朗的而又美好的一天,可是小明的妈妈却让...
  • 【C++】构造函数 利用构造函数对类对象进行初始

    千次阅读 多人点赞 2022-01-25 16:01:26
    有关构造函数与析构函数的知识点

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 183,251
精华内容 73,300
热门标签
关键字:

不同名函数化同名函数