C- 声明为静态的函数的链接

Posted

技术标签:

【中文标题】C- 声明为静态的函数的链接【英文标题】:C- Linkage of functions declared static 【发布时间】:2013-09-10 12:14:57 【问题描述】:

声明为静态的函数和变量具有内部链接,并且它们具有文件范围,并且它们对其他文件中的函数不可见。

假设我声明一个这样的函数:-

  static int foo(int i);

在一个名为 file1.c 的文件中 我可以通过使用指针从其他文件file2.c访问这个函数吗?

我正在浏览一本书,书中写到它可以做到,但我不知道这怎么可能。

这些是确切的行:-

因为它有内部链接,所以不能直接调用 foo 在定义它的文件之外。(将 foo 声明为静态不会 完全防止它在另一个文件中被调用;间接的 仍然可以通过函数指针调用)。

【问题讨论】:

如果您想从其他编译单元访问该函数,那么您为什么要首先使用static @Jon:您可以将回调声明为静态(没有其他人可以通过其标识符调用该函数),并将其指针发送到设置函数以对事件作出反应。你强迫它成为一个回调函数,而不是像一个普通的那样调用。 @Gauthier:在这种情况下,您只需将地址传递给被调用者:takes_callback(foo) - 我看到您已经建议了。 OP 要求(正如我和其他回答的人看到的那样)提供一个全局可访问的函数指针,这完全没有意义。当然,这并不妨碍人们回答 moar rep ... 的字面问题 只有拥有静态函数的模块从不改变指针的值,这完全没有意义。但是拥有模块可以在内部做出复杂的决定,并改变例如在调用指针时运行哪个版本的算法(对于调用模块是透明的)。 static 与“范围”无关,无论是函数还是对象。问题中对“范围”的提及具有误导性。标识符的范围取决于在哪里声明它并且完全独立于任何声明说明符(如static)。 【参考方案1】:

也许你正在寻找这个?

// file1.h
extern void (*fptr)(void);

// file1.c
static void foo(void)

    printf("Hello world!\n");


void (*fptr)(void) = &foo;  //or void (*fptr)(void) = foo;

// file2.c
#include "file1.h"

fptr();

这里,foo 是静态的,但它的地址是通过非static 全局变量引用的。这是完全可能的。

【讨论】:

【参考方案2】:

该函数不能在任何其他文件中按名称调用,因为它在不同文件中是静态的,但您可以使用指向它的函数指针来执行此操作。

extern int (*pf)(int);

你需要将foo分配给这个指针,然后你才能访问它。

【讨论】:

【参考方案3】:

H2CO3 以另一种方式为您提供正确答案:

/* a.h */
typedef int (*fptr)(int);

fptr call_foo(void);

/* a.c */
#include "a.h"

static int foo(int i)

    return i * 2;


fptr call_foo(void)

    return foo;


/* main.c */
#include <stdio.h>
#include "a.h"

int main(void)

    fptr fn = call_foo();

    printf("%d\n", fn(2));
    return 0;

【讨论】:

【参考方案4】:

另一种我认为可行的方法是使您的静态函数成为回调。

//------------------------------
// file react.c
#include "event.h"

static void react_on_event_A(void)

    // do something if event_A occurs.


void react_init(void)

    event_setup(EVENT_A, react_on_event_A);

在这里,您将react_on_event_A 函数设置为事件驱动程序可以调用的回调,但阻止其他任何人通过其标识符调用该函数。你真的是在告诉其他人不要使用该功能。

事件驱动程序可能如下所示:

//------------------------------
// file event.h
typedef enum 
    EVENT_A,
 event_t;

void event_setup(event_t event, void (*callback)(void));
void event_check_and_run(void);


//------------------------------
// file event.c
static void (*event_A_callback)(void);

void event_setup(event_t event, void (*callback)(void))

    if (event == EVENT_A) 
        event_A_callback = callback;
    


// Scheduled periodically, or reacting on interrupt.
void event_check_and_run(void)

    if (occured(EVENT_A) && (event_A_callback != NULL)) 
        event_A_callback();
    

这样做的好处是模块react 控制哪些其他模块(在这种情况下为event)可以访问它自己的静态函数。

使用其他替代方案(是否创建函数static,或在头文件中发布一个指针),您可以不授予任何人访问权限,也可以授予所有人访问权限。

【讨论】:

【参考方案5】:

简而言之:是的,您可以通过指针访问静态方法。

要理解这一点,最好多了解一下编译器的底层原理。

为清楚起见,已编译的程序是用机器代码编写的。 “程序加载器”的编译程序中有额外的信息,但程序本身只是处理器执行的指令。

当您在 C 中调用函数“foo()”时,C 编译器会将其转换为“CALL”处理器操作。 CALL 操作在代码中后跟 foo 的地址(字面意思是内存地址或“偏移量”)。请注意,因为它是一个内存地址,所以没有使用名称(“foo”)。另请注意,链接器不需要知道“foo”就可以工作。

当您在 C 中调用函数“bar()”并且该函数位于另一个编译单元(另一个 C 文件)中时,编译器会出现一些问题,因为它不知道程序中的位置(内存中的位置) ) 函数是调用。那就是它不知道在 CALL 操作之后要写什么地址。发生这种情况时,它会编写代码,为地址留出空间,但为链接器留下注释。注释告诉链接器“把 bar 的地址放在这里”。所以链接器使用内存地址更正编写的程序。允许链接器执行此操作;编译器在代码中编写一个包含每个函数名称和相应地址的表。

那么静态有什么作用呢?这只是告诉编译器不要在传递给链接器的表中写入函数的名称和地址。该函数仍然作为函数存在于代码中,但链接器不知道它在哪里。同一编译单元中的任何代码都将知道函数在哪里。所以同一编译单元内的任何函数都可以将函数的地址作为编译单元外的指针传递。

用于传递函数指针的 c 代码是这样的:

file1.h

typedef void (* VoidFunctionPointer)();

extern VoidFunctionPointer somethingInteresting;

bar();

file1.c

#include "a.h"
VoidFunctionPointer somethingInteresting;

static void foo() 
    // do something interesting;



void bar() 
    // we know what foo is because we're in the same compilation unit
    somethingInteresting = foo;

file2.c

#include "a.h"

int main(int argC, char ** argV) 
        bar();
        // we can call somethingInteresting as if it is a function no matter
        // wether it's declared static in code or not
        // this can be foo, just as long as we don't have to use the name "foo"
        somethingInteresting();

在这段代码中,file2 实际上运行了来自 file1 的静态函数。关键是 file2 永远不需要该函数的名称,因此 static 对函数指针没有影响。

我可以推荐阅读微软对 PE 格式(.EXE 和 .DLL)的描述 [这里]:

http://msdn.microsoft.com/en-us/library/ms809762.aspx

【讨论】:

file2 永远不需要名称这一事实是正确的,但强大的是,file1 可以更改somethingInteresting 指向的函数,而无需告诉任何人。 @Gauthier 是的,你是对的,函数指针并不是为了规避 static 限制而存在的。这只是一个副作用。

以上是关于C- 声明为静态的函数的链接的主要内容,如果未能解决你的问题,请参考以下文章

将线程函数声明为静态函数的问题

C++ 静态模板函数可以在具有 C 链接的结构中吗?

为啥 C++11 不支持在静态成员函数上声明 extern "C"?

c语言静态函数调用问题

嵌入式 Linux C语言——静态库函数和动态库函数

C#/WPF高手进!关于依赖属性、附加属性等声明方式以及静态构造函数、静态属性等问题。