• c语言tips-c语言的虚函数实现


    0. 前言

    学过面对对象的同学都知道虚函数是面向对象编程中的一个重要概念,它允许在基类和派生类之间实现多态性(polymorphism)。我们可以在基类去定义一个成员函数,然后再派生类再去覆盖写它,这样在不同派生类使用相同函数名就可以实现不同的功能。下面可以看一下c++和python是如何做的

    1. 面对对象语言实现

    cpp

    #include 
    
    class Base {
    public:
        virtual void foo() {
            std::cout << "Base::foo() called" << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        void foo() override {
            std::cout << "Derived::foo() called" << std::endl;
        }
    };
    
    int main() {
        Base* ptr = new Derived();  // 使用基类指针指向派生类对象
        ptr->foo();  // 调用派生类中的虚函数
    
        delete ptr;  // 释放内存
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这是cpp的虚函数实现,当子类继承基类后如果没有重写foo()函数,则就输出Base::foo() called,如果重写了则会输出Derived::foo() called

    python

    from abc import ABC, abstractmethod
    
    class MyAbstractClass(ABC):
        @abstractmethod
        def my_abstract_method(self):
            pass
    
    class MyDerivedClass(MyAbstractClass):
        pass
    
    obj = MyDerivedClass()  # 引发 TypeError 异常,因为未实现抽象方法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这个例子中,MyAbstractClass 是一个抽象类,其中的 my_abstract_method 是一个抽象方法。MyDerivedClass 派生自 MyAbstractClass,但没有提供对 my_abstract_method 的具体实现,因此在实例化 MyDerivedClass 对象时会引发异常。这其实有点类似于cpp的纯虚函数了,需要强制在子类重写具体的方法
    抽象类的优势在于它可以提供一种约束,确保派生类实现了抽象类中定义的所有抽象方法。这有助于编写更可靠和可维护的代码,并在运行时捕获未实现的方法调用。同时,抽象类也可以提供公共的默认实现,以减少派生类的代码重复。

    2. c语言实现

    由于在做维护sdk的工作,很多时候我们不希望去改底层的一些代码而实现不同客户需要的功能,我就在想c语言能不能有一个类似于虚函数的功能来根据编译的文件中不同的同名函数而实现不同的功能呢?然后我就发现了__attribute__((weak))

    • 以下是__attribute__((weak))的介绍

    • attribute((weak))是一种GCC编译器的属性(attribute),用于将符号(函数或变量)标记为弱引用。 在C语言中,当你声明一个函数或变量时,编译器会生成一个对应的符号。当链接器在不同的编译单元(源文件)中遇到相同的符号时,它需要解决这些符号的引用。通常情况下,链接器会选择具有强引用的符号作为最终的定义。

    • 然而,通过使用__attribute__((weak))属性,你可以将一个符号标记为弱引用。这意味着如果在链接过程中存在具有强引用的符号定义,那么它将被选择作为最终的定义;否则,将使用具有弱引用的符号定义。

    • 这种覆盖行为是因为具有强引用的函数定义优先于具有弱引用的函数定义。在链接过程中,链接器会选择具有强引用的函数定义,而忽略具有弱引用的函数定义。

    • 需要注意的是,覆盖具有__attribute__((weak))属性的函数时,函数签名(函数名和参数列表)必须完全匹配。否则,链接器将无法正确解析符号引用。

    • 此外,当覆盖具有__attribute__((weak))属性的函数时,覆盖函数的定义必须在链接器解析符号引用之前可用。否则,强引用的函数定义将无法覆盖弱引用的函数定义。

    说再多概念不如上一个例子
    在这里插入图片描述

    我编写了三个.c文件,main.c去调用test1.c test2.c的函数,代码分别如下

    main.c

    #include 
    
    extern void func_print1();
    extern void func_print2();
    
    
    // void my_function1(void)
    // {
    //     printf("This is the overriding function.\n");
    // }
    
    // void my_function2(void)
    // {
    //     printf("This is the overriding function.\n");
    // }
    
    
    int main()
    {
        func_print1();
        func_print2();
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    test1.c

    // test1.c
    #include 
    
    void __attribute__((weak)) my_function1(void) {
        printf("This is the weak function. from test1.c \n");
    }
    
    void func_print1()
    {
        my_function1();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    test2.c

    // test2.c
    #include 
    
    void __attribute__((weak)) my_function2(void)
    {
        printf("This is the weak function. from test2.c \n");
    }
    
    void func_print2()
    {
        my_function2();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    毫无疑问,现在输出的肯定是
    This is the weak function. from test1.c
    This is the weak function. from test2.c

    假设我们不想改变test1.c test2.c的代码而只在main.c修改代码来影响底层的操作,那我们就可以在main.c去写一个同名的函数去覆盖它们

    #include 
    
    extern void func_print1();
    extern void func_print2();
    
    
    void my_function1(void)
    {
        printf("This is the overriding function1.\n");
    }
    
    void my_function2(void)
    {
        printf("This is the overriding function2.\n");
    }
    
    
    int main()
    {
        func_print1();
        func_print2();
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    This is the overriding function1.
    This is the overriding function2.

    这在sdk的开发中可以带来三个好处

    • 可选性覆盖:使用 attribute((weak)) 属性声明的函数或变量可以在链接时被覆盖。这意味着,在SDK的使用者中可以选择是否提供自定义的实现来替换SDK中的默认实现。这种灵活性使得SDK更加通用和可配置。
    • 默认实现:通过在SDK中使用弱符号,可以为函数或变量提供默认实现。如果SDK的使用者没有提供自定义的实现,编译器会选择使用SDK中的默认实现。这样可以减少使用SDK的开发者的工作量,同时保证SDK的功能完备性。
    • 扩展性:使用弱符号属性可以为SDK的使用者提供扩展接口。使用者可以通过覆盖弱符号来添加新的功能、修改行为或提供自定义的回调函数。这种扩展性使得SDK适应不同的应用场景和需求。

    3. 总结

    虽然c语言是个面向过程的语言,但是使用__attribute__((weak))属性依旧能够实现面向对象的虚函数的概念,在某些场合中对于整体代码的维护和开发有着重大作用。全网好像也没有比较详细的对__attribute__((weak))属性比较详细的解释,如果你也遇到这个属性的问题,希望这篇文章能够帮到你!有帮助的话希望能给我点个赞吧

  • 相关阅读:
    libusb 源码移植到工程项目中,使用CMake编译
    IT运维管理平台助力企业打造监、管、控一体化
    蓝牙安全入门——两道CTF题目复现
    Jpeg文件格式详解
    Linux上文本处理三剑客之grep
    深入理解Go语言接口
    快速搜索多个word、excel等文件中内容
    Go与数据库:NoSQL数据库的应用
    10个适合后端程序员的前端框架
    【SpringBoot】之接口设计防篡改和防重放攻击
  • 原文地址:https://blog.csdn.net/weixin_46187354/article/details/132525730