如何检查 C/C++ 中某个函数是否存在?

2024-11-13 08:36:00
admin
原创
20
摘要:问题描述:在我的代码中,某些情况下,我只会在定义了该函数时才调用该函数,否则就不应该调用。我该如何实现这一点?like: if (function 'sum' exists ) then invoke sum () 也许反过来问这个问题是如何确定函数是否在运行时定义,如果是,那么调用?解决方案 1:使用 GC...

问题描述:

在我的代码中,某些情况下,我只会在定义了该函数时才调用该函数,否则就不应该调用。我该如何实现这一点?

like:
if (function 'sum' exists ) then invoke sum ()

也许反过来问这个问题是如何确定函数是否在运行时定义,如果是,那么调用?


解决方案 1:

使用 GCC 您可以:

void func(int argc, char *argv[]) __attribute__((weak)); // weak declaration must always be present

// optional definition:
/*void func(int argc, char *argv[]) { 
    printf("FOUND THE FUNCTION
");
    for(int aa = 0; aa < argc; aa++){
        printf("arg %d = %s 
", aa, argv[aa]);
    }
}*/

int main(int argc, char *argv[]) {
    if (func){ 
        func(argc, argv); 
    } else {
        printf("did not find the function
");
    }
}

如果取消注释 func,它将运行它,否则它将打印“未找到该函数\n”。

解决方案 2:

当您声明“sum”时,您可以像这样声明:

#define SUM_EXISTS
int sum(std::vector<int>& addMeUp) {
    ...
}

然后当你使用它时你可以这样做:

#ifdef SUM_EXISTS
int result = sum(x);
...
#endif

我猜你来自一种脚本语言,其中所有事情都是在运行时完成的。使用 C++ 时要记住的主要事情是两个阶段:

  • 编译时

    • 预处理器运行

    • 模板代码变成真正的源代码

    • 源代码转换成机器代码

  • 运行时

    • 运行机器代码

所以所有#define类似的事情都发生在编译时。

....

如果您真的想在运行时完成所有操作,您可能会对使用一些组件架构产品感兴趣。

或者也许您追求的是插件类型的架构。

解决方案 3:

虽然其他回复都是有用的建议(dlsym、函数指针等),但您无法编译引用不存在的函数的 C++ 代码。至少,必须声明该函数;如果没有,您的代码将无法编译。如果没有任何东西(编译单元、某个目标文件、某个库)定义该函数,链接器就会抱怨(除非它很弱,见下文)。

但你确实应该解释一下你为什么问这个问题。我猜不出来,而且一定有某种方法可以实现你未说明的目标。

请注意,dlsym通常需要没有名称修改的函数,即声明为extern "C"

如果在 Linux 上使用 GCC 进行编码,您可能还会在声明中使用weak 函数属性。然后链接器会将未定义的弱符号设置为空。

附加物

如果您从某些输入中获取函数名称,则应该知道只有一部分函数可以通过这种方式调用(如果您不小心调用任意函数,它就会崩溃!),最好明确构造该子集。然后,您可以使用std::map,或dlsym(子集中的每个函数都声明为extern "C")。请注意,dlopen使用NULL路径会为主程序提供句柄,您应该将其链接起来-rdynamic以使其正常工作。

您确实希望仅通过其名称调用适当定义的函数子集。例如,您可能不想以这种方式调用abortexitfork

注意:如果您动态地知道被调用函数的签名,则可能需要使用libffi来调用它。

解决方案 4:

我怀疑发帖人实际上正在寻找更多类似于 SFINAE 检查/调度的东西。使用 C++ 模板,可以定义两个模板函数,一个调用所需函数(如果存在),另一个不执行任何操作(如果函数不存在)。然后,您可以使第一个模板依赖于所需函数,这样当函数不存在时,模板格式不正确。这是有效的,因为在 C++ 中模板替换失败不是错误(SFINAE),因此编译器将返回到第二种情况(例如,什么也不做)。

请参阅此处的一个很好的例子:是否可以编写模板来检查函数是否存在?

解决方案 5:

使用指向函数的指针。

 //initialize
 typedef void (*PF)();
 std::map<std::string, PF> defined_functions;
 defined_functions["foo"]=&foo;
 defined_functions["bar"]=&bar;
 //if defined, invoke it
 if(defined_functions.find("foo") != defined_functions.end())
 {
     defined_functions["foo"]();
 }

解决方案 6:

如果您知道想要调用的函数在哪个库中,那么您可以使用dlsym()dlerror()来查明它是否存在,以及指向该函数的指针是什么。

编辑:我可能不会真正使用这种方法 - 相反,我会推荐 Matiu 的解决方案,因为我认为这是一个更好的做法。但是,dlsym()它并不为人所知,所以我想指出这一点。

解决方案 7:

您可以使用#pragma weak支持它的编译器(参见弱符号维基百科条目)。

此示例和评论来自共享库和动态加载的内幕故事:

#pragma weak debug
extern void debug(void);
void (*debugfunc)(void) = debug;
int main() {
    printf(“Hello World
”);
    if (debugfunc) (*debugfunc)();
}

您可以使用弱指令强制链接器忽略未解析的符号[..],无论 debug() 是否在任何目标文件中实际定义,程序都会编译和链接。当符号仍未定义时,链接器通常会将其值替换为 0。因此,对于程序来说,这种技术可以成为一种有用的方法,用于调用不需要重新编译整个应用程序的可选代码。

解决方案 8:

因此,如果您使用 c++11,另一种方法是使用函子:

您需要将其放在文件的开头:

#include <functional>

函子的类型声明格式如下:

std::function< return_type (param1_type, param2_type) >

您可以添加一个保存 sum 函子的变量,如下所示:

std::function<int(const std::vector<int>&)> sum;

为了简单起见,我们缩短参数类型:

using Numbers = const std::vectorn<int>&;

然后你可以用以下任何一个来填充函子var:

lambda表达式:

sum = [](Numbers x) { return std::accumulate(x.cbegin(), x.cend(), 0); } // std::accumulate comes from #include <numeric>

函数指针:

int myFunc(Numbers nums) {
    int result = 0;
    for (int i : nums)
        result += i;
    return result;
}
sum = &myFunc;

“bind” 创建了一些内容:

struct Adder {
    int startNumber = 6;
    int doAdding(Numbers nums) {
        int result = 0;
        for (int i : nums)
            result += i;
        return result;
    }
};
...
Adder myAdder{2}; // Make an adder that starts at two
sum = std::bind(&Adder::doAdding, myAdder);

最后使用它,这是一个简单的 if 语句:

if (sum)
    return sum(x);

总结一下,函子是指向函数的新指针,但它们的用途更广泛。如果编译器足够确定,实际上可以内联,但通常与函数指针相同。

当与 std::bind 和 lambda 结合时,它们比旧式 C 函数指针要优越得多。

但请记住它们在 c++11 及以上环境中有效。(不适用于 C 或 C++03)。

解决方案 9:

在 C++ 中,检查成员是否存在的技巧的修改版本应该在编译时而不是运行时为您提供您想要的内容:

#include <iostream>
#include <type_traits>

namespace
{
    template <class T, template <class...> class Test>
    struct exists
    {
        template<class U>
        static std::true_type check(Test<U>*);

        template<class U>
        static std::false_type check(...);

        static constexpr bool value = decltype(check<T>(0))::value;
    };
    
    template<class U, class = decltype(sum(std::declval<U>(), std::declval<U>()))>
    struct sum_test{};
    
    template <class T>
    void validate_sum()
    {
        if constexpr (exists<T, sum_test>::value)
        {
            std::cout << "sum exists for type " << typeid(T).name() << '
';
        }
        else
        {
            std::cout << "sum does not exist for type " << typeid(T).name() << '
';
        }
    }
    
    class A {};
    class B {};
    
    void sum(const A& l, const A& r); // we only need to declare the function, not define it
}

int main(int, const char**)
{
    validate_sum<A>();
    validate_sum<B>();
}

以下是使用 clang 的输出:

sum exists for type N12_GLOBAL__N_11AE
sum does not exist for type N12_GLOBAL__N_11BE

我应该指出,当我使用 int 而不是 A 时,发生了奇怪的事情(sum()必须事先声明sum_test才能exists工作,所以可能exists不是这个的正确名称)。当我使用 A 时,某种模板扩展似乎不会引起问题。我猜它与 ADL 有关。

解决方案 10:

在 c 中,你可以使用函数指针数组

#include<stdio.h>
#include<string.h>

typedef struct i32_2arg{int a, b;}i32_2_arg;
typedef struct i32_arg{int a;}i32_arg;

void add(void*a,void*r){
  i32_2_arg* l = (i32_2_arg*)a;
  ((i32_arg*)r)->a = l->a+l->b;
}
void sub(void*a,void*r){}

char lFunNames[8][64] = {"add","sub",0};
void (*lFuns[8]) (void*,void*) = {&add,&sub,0};

void evalfun(char* lName, void* inargs,void* outargs){
  for(int i = 0; i < 8; i++ )
  if (!strcmp(lFunNames[i],lName)) (*lFuns[i])(inargs,outargs);
}

int main(){
  i32_2_arg ab ={2,3};
  i32_arg sum;
  evalfun("add",&ab,&sum);
  printf("if \"add\" exists, result is %d
",sum.a);
  return 0;
}

解决方案 11:

此答案针对全局函数,作为对测试方法的其他答案的补充。此答案仅适用于全局函数。

首先,在单独的命名空间中提供一个 fallback 虚拟函数。然后在模板参数中确定函数调用的返回类型。根据返回类型,确定这是 fallback 函数还是所需函数。

如果禁止在函数的命名空间中添加任何内容,例如 for 的情况std::,那么您应该使用ADL在测试中找到正确的函数。

例如,std::reduce()是 c++17 的一部分,但早期的 gcc 编译器(应该支持 c++17)没有定义std::reduce()。以下代码可以在编译时检测是否std::reduce声明。在编译资源管理器中查看它在两种情况下是否正常工作。

#include <numeric>

namespace fallback
{
    // fallback
    std::false_type reduce(...) { return {}; }

    // Depending on
    // std::recuce(Iter from, Iter to) -> decltype(*from)
    // we know that a call to std::reduce(T*, T*) returns T
    template <typename T, typename Ret = decltype(reduce(std::declval<T*>(), std::declval<T*>()))>
    using return_of_reduce = Ret;

    // Note that due to ADL, std::reduce is called although we don't explicitly call std::reduce().
    // This is critical, since we are not allowed to define any of the above inside std::
}

using has_reduce = fallback::return_of_reduce<std::true_type>;

// using has_sum = std::conditional_t<std::is_same_v<fallback::return_of_sum<std::true_type>, 
//                                                   std::false_type>,
//                                    std::false_type,
//                                    std::true_type>;

#include <iterator>
int main()
{
    if constexpr (has_reduce::value)
    {
        // must have those, so that the compile will find the fallback
        // function if the correct one is undefined (even if it never
        // generates this code).
        using namespace std;
        using namespace fallback;
        int values[] = {1,2,3};
        return reduce(std::begin(values), std::end(values));
    }
    return -1;
}

与上述示例不同,当您无法控制返回类型时,可以使用其他方法,例如std::is_samestd::contitional

例如,假设您要测试函数是否int sum(int, int)在当前编译单元中声明。以类似的方式创建。test_sum_ns::return_of_sum如果函数存在,则为,否则intstd::false_type(或您喜欢的任何其他特殊类型)。

using has_sum = std::conditional_t<std::is_same_v<test_sum_ns::return_of_sum, 
                                                  std::false_type>,
                                   std::false_type,
                                   std::true_type>;

然后您可以使用该类型:

if constexpr (has_sum::value) 
{
   int result;
   {
      using namespace fallback; // limit this only to the call, if possible.
      result = sum(1,2);
   }
   std::cout << "sum(1,2) = " << result << '
';
}

注意:必须有using namespace,否则编译器将找不到 中的 fallback 函数if constexpr,并会报错。一般来说,你应该避免,using namespace因为命名空间内符号的未来变化可能会破坏你的代码。在这种情况下,没有其他办法,所以至少要将其限制在尽可能小的范围,如上例所示

解决方案 12:

这是另一种方法:将可选方法声明为结构内的虚方法,然后使用该结构作为实现该方法的派生对象的基类。

示例(包括用例概要):

#include <map>                      // For std::map
#include <string>                   // For std::string
#include <stdio.h>                  // For printf

//----------------------------------------------------------------------------
class Basic {
public:
typedef std::map<std::string, Basic*>         Map_t; // The Map type
static Map_t           map;         // The Global map

   Basic(std::string name)
{  map[name]= this; }

virtual
   ~Basic( void )
{  }

// Optional methods- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct has_stop {
virtual void
   stop( void )
{  }
}; // struct has_stop

struct has_wait {
virtual void
   wait( void )
{  }
}; // struct has_wait
}; // class Basic
Basic::Map_t Basic::map;

//----------------------------------------------------------------------------
class Global {
public:
void
   stop( void )
{
   Basic::Map_t& map= Basic::map;
   for(auto const& mi: map) {
     Basic::has_stop* method= dynamic_cast<Basic::has_stop*>(mi.second);
     if( method )
       method->stop();
     else
       printf("Basic(%s) doesn't have stop()
", mi.first.c_str());
   }
}

void
   wait( void )
{
   Basic::Map_t& map= Basic::map;
   for(auto const& mi: map) {
     Basic::has_wait* method= dynamic_cast<Basic::has_wait*>(mi.second);
     if( method )
       method->wait();
     else
       printf("Basic(%s) doesn't have wait()
", mi.first.c_str());
   }
}
}; // class Global
static Global global;

//----------------------------------------------------------------------------
class One : public Basic {
public:
   One(std::string name)
:  Basic(name) {}

// While the stop method is present, has_stop is not a base class.
// Global::stop won't find the method.
virtual void stop( void )
{  printf("One::stop
"); }
}; // class One

//----------------------------------------------------------------------------
class Two
   : public Basic
   , public Basic::has_stop, public Basic::has_wait {
public:
   Two(std::string name)
:  Basic(name) {}

virtual void stop( void )           // (Overrides Basic::has_stop)
{  printf("Two::stop
"); }

virtual void wait( void )           // (Overrides Basic::has_wait)
{  printf("Two::wait
"); }
}; // class Two

//----------------------------------------------------------------------------
int main( void ) {
   One one("one");
   Two two("two");

   one.stop();                      // We can invoke One::stop()
   global.stop();                   // But Global::stop won't find it
   global.wait();

   return 0;
} // main

输出:

One::stop
Basic(one) doesn't have stop()
Two::stop
Basic(one) doesn't have wait()
Two::wait

另一个(更简单的)选项是始终将函数包含在基类中,并且基类实现为空,除了返回函数签名所需的内容。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用