如何从一个C源代码文件中调用另一个C源代码文件中的静态函数?
摘要:
1. 你不应该从另一个源文件中调用一个静态函数。静态意味着“我不应该在其他地方使用”。所以,如果你真的需要从另一个源文件中使用该函数或变量,请从其声明中删除static。
- 请参阅下面“详细信息”部分的顶部。
2. 但是,如果你真的需要(例如:用于单元测试,或者在对第三方库进行最小更改的同时修改它),请参阅下面标题为“强制在另一个文件中包含静态函数或变量的技术”的部分。
此答案还回答了以下问题:“何时在C或C++中使用extern?”以及“C和C++中的extern vs static”。
首先,通常情况下,你不应该在另一个文件中使用静态函数或变量。如果你有一个在其他源文件中使用的函数,它应该是非静态的,并且在头文件中声明,然后在需要它的源文件中包含该头文件。这是正确的设计模式。然后在使用该函数的每个源文件中包含该头文件。有关此示例,请参阅下面的“#include vs 前向声明”部分。
如果你有一个只在一个文件中使用的函数或变量,它应该被声明为静态,这样它就不会在该文件之外可见,无论如何。静态关键字给函数或变量提供了内部链接或者俗称的“文件作用域”,这意味着它只在声明它的文件内可见。
这些答案也是这样说的。
@Anbu.Sankar
@vivek thantho
@Umamahesh P
@Frodo
@Teodorico Levoff
但是,我可以想到两种情况,你可能需要在另一个文件中包含一个static
函数:
你正在尝试对一个.c源文件中的私有静态函数进行单元测试。
你正在尝试增强一些第三方库的代码,同时对该代码进行零或最小限度的更改。当第三方库代码处于活跃开发状态时,希望对第三方库代码进行零或最小限度的更改,这样你就可以轻松升级到未来版本的代码,而不必解开你对其副本所做的更改。以我的情况为例,来自
我的评论:
我现在就处于这种情况。情况是我需要增强一些FreeRTOS内核代码以注入一些额外的调试功能。然而,为了使我的代码模块化并且不依赖于FreeRTOS内核,以便我可以轻松升级FreeRTOS版本,我需要从定义了该静态C函数的文件之外访问FreeRTOS [
tasks.c
内核文件]中的一个私有静态C函数。因此,我认为在尝试增强第三方库的代码时,但又不触及(修改)其源代码时,这种情况最常见。
对于在.c文件中定义的私有静态函数进行单元测试是另一个合理的用例。
所以,如果你真的需要在另一个文件中调用一个静态函数,这里有一些方法。
背景知识:
首先,一些基本知识:
1. extern vs static
关键字extern和static是彼此的对立面。extern表示“这个变量或函数在另一个文件中定义”,而static表示“这个变量或函数只在这个作用域内可见”。通常,静态变量的作用域在它所在的花括号{}内(通常是一个函数),但如果静态变量在所有函数之外定义,则它是一个静态全局变量,其作用域是整个文件。如果一个函数或变量是非静态的,则其作用域是整个程序中的所有文件,只要它们满足以下条件之一:
将每个声明的头文件包含在其中,或者在要使用它们的文件中提前声明函数或变量。
没有`static`的函数默认为`extern`,所以你不需要在它们前面显式地写`extern`。但是,如果你愿意的话,你可以这样做:
extern void foo(void);
void foo(void);
所以,在头文件中,当你写一个函数声明时,你不需要在它前面写上
extern
,因为它默认就是
extern
。
另一方面,
变量默认情况下不是extern
,所以如果你想让它们成为
extern
,你需要明确地在它们前面写上
extern
。
uint32_t u32;
extern uint32_t u32;
在
foo.c
中的静态全局函数或变量:
static void foo(void) {
}
static uint32_t u32;
2. #include与前向声明
假设您有以下源文件。您想在该文件之外使用函数foo()和变量u32:
foo.c
:
void foo(void) {
}
uint32_t u32;
要访问另一个文件中定义的非静态函数或变量,你有两个选项:
1. 在你想要使用它的文件中,像这样提前声明它的存在:
main.c:
// 函数`foo`的前向声明,这样你就可以在这个文件中使用它。
// - 记住:对于函数,`extern`是自动隐含的!
void foo();
// 同样的事情,但是显式地使用`extern`
extern void foo();
// 变量`u32`的前向声明,在另一个文件中定义,所以你可以在这个文件中使用它。
// - 记住:对于变量,`extern`不是自动隐含的,所以你必须在这里**显式地**写上`extern`。
extern uint32_t u32;
// 现在你可以在这个文件中调用foo(),并且使用来自另一个文件的变量`u32`。
int main()
{
foo();
printf("u32 = %u\n", u32);
u32 = 123;
return 0;
}
2. 使用`#include "foo.h"`语句更好:
然而,一种更常规和推荐的方法是将前向声明放入一个`.h`头文件中,然后在需要访问这些函数和变量的地方包含该头文件。
这是一个示例`foo.h`头文件来实现这一点:
foo.h:
#pragma once
// 前向声明在foo.c中定义的`foo()`函数;对于函数,`extern`在这里是自动隐含的,所以你不必写它。
void foo();
// 前向声明在`foo.c`中分配和定义的`uint32_t u32`变量的存在。
extern uint32_t u32;
然后,你只需要包含这个头文件,这些前向声明就会在编译时由预处理器复制到你的文件中:
main.c:
#include "foo.h"
// 现在你可以在这个文件中调用foo(),并且像之前手动写前向声明一样使用来自另一个文件的变量`u32`!
int main()
{
foo();
printf("u32 = %u\n", u32);
u32 = 123;
return 0;
}
评论/总结
所以,这些都可以使用:
main.c
:
#include "foo.h"
void foo();
extern void foo();
extern uint32_t u32;
int main()
{
foo();
printf("u32 = %u\n", u32);
u32 = 123;
return 0;
}
如果您尝试从另一个文件调用一个静态函数或变量,将会出现以下错误示例:
在Ubuntu 11.4.0-1ubuntu1~22.04上使用
gcc --version
进行了测试。要在Windows上运行这些
gcc
构建命令,请使用MSYS2终端。请参阅我的完整设置说明:
从头开始安装和设置MSYS2,包括将所有7个配置文件添加到Windows终端。
这对于帮助您理解在您自己的代码中遇到此问题时非常有帮助:
如果您尝试从另一个文件进行前向声明或包含一个静态函数或变量,将会出现以下
构建(链接器,ld
)错误的示例:
undefined reference to `counter'
undefined reference to `print_incrementing_number'
链接步骤是构建过程的最后一步,它尝试在已编译的目标文件
.o
中找到并匹配函数声明与其已编译的定义。如果一个函数或变量被构建为
static
,它具有"静态链接",这意味着它在定义的文件之外是不可见的,因此链接器无法找到它,从而导致出现此错误。
链接器(
ld
程序)无法找到我的
print_incrementing_number()
函数和
uint32_t counter
变量。如果您想尝试运行命令,请查看我
eRCaGuy_hello_world存储库中的提交
11430a3cb3b298f26d3763e4a7224a7d610751c1
。
C语言完整构建错误:
eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=gnu17 static_extern_function_include__main.c static_extern_function_include__module.c -o bin/a && bin/a
/usr/bin/ld: /tmp/ccKPOHqx.o: warning: relocation against `counter' in read-only section `.text.startup'
/usr/bin/ld: /tmp/ccKPOHqx.o: in function `main':
static_extern_function_include__main.c:(.text.startup+0x1b): undefined reference to `print_incrementing_number'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x21): undefined reference to `counter'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x37): undefined reference to `print_incrementing_number'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x3d): undefined reference to `counter'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x53): undefined reference to `print_incrementing_number'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x59): undefined reference to `counter'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
完整的C++构建错误:
eRCaGuy_hello_world/c$ g++ -Wall -Wextra -Werror -O3 -std=gnu++17 static_extern_function_include__main.c static_extern_function_include__module.c -o bin/a && bin/a
/usr/bin/ld: /tmp/ccAn3aKq.o: warning: relocation against `counter' in read-only section `.text.startup'
/usr/bin/ld: /tmp/ccAn3aKq.o: in function `main':
static_extern_function_include__main.c:(.text.startup+0x19): undefined reference to `print_incrementing_number()'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x1f): undefined reference to `counter'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x33): undefined reference to `print_incrementing_number()'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x39): undefined reference to `counter'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x4d): undefined reference to `print_incrementing_number()'
/usr/bin/ld: static_extern_function_include__main.c:(.text.startup+0x53): undefined reference to `counter'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
这是产生错误的源代码:
static_extern_function_include__module.c
:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
static uint32_t counter = 0;
static void print_incrementing_number()
{
printf("counter = %u\n", counter);
counter++;
}
void foo()
{
print_incrementing_number();
}
static_extern_function_include__main.c
:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
void print_incrementing_number();
extern uint32_t counter;
int main()
{
printf("Hello World.\n");
print_incrementing_number();
printf(" counter = %u\n", counter);
print_incrementing_number();
printf(" counter = %u\n", counter);
print_incrementing_number();
printf(" counter = %u\n", counter);
return 0;
}
构建命令:
gcc -Wall -Wextra -Werror -O3 -std=gnu17 static_extern_function_include__main.c static_extern_function_include__module.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=gnu++17 static_extern_function_include__main.c static_extern_function_include__module.c -o bin/a && bin/a
修复方法:强制将static
函数或变量包含在另一个文件中的技巧
1. 可能的解决方案:
[Preferred choice if you intend the function and variable to be used elsewhere] remove the static
keyword from the .c
file:
Go to static_extern_function_include__module.c
and remove the static
keyword from the function and variable declarations, so you have this:
uint32_t counter = 0;
void print_incrementing_number()
{
printf("counter = %u\n", counter);
counter++;
}
Now it builds and runs. Here is my command and output:
eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=gnu17 static_extern_function_include__main.c static_extern_function_include__module.c -o bin/a && bin/a
Hello World.
counter = 0
counter = 1
counter = 1
counter = 2
counter = 2
counter = 3
The build commands for all other techniques below are exactly the same as just above unless stated otherwise.
[My preferred choice for enhancing a 3rd-party library] write a non-static wrapper function in the bottom of their .c
file for access to static functions, and setter and getter functions for access to their static variables:
At the bottom of their .c
file, add your custom wrappers and setters/getters, like this:
static_extern_function_include__module.c
:
static uint32_t counter = 0;
static void print_incrementing_number()
{
printf("counter = %u\n", counter);
counter++;
}
void foo()
{
print_incrementing_number();
}
void print_incrementing_number_wrapper()
{
print_incrementing_number();
}
void set_counter(uint32_t new_counter)
{
counter = new_counter;
}
uint32_t get_counter()
{
return counter;
}
In your .c
file, you can now forward declare and use these custom wrappers and setters/getters.
Even better, make a wrapper .h
file too:
static_extern_function_include__module_wrapper.h
:
#pragma once
#include <stdint.h>
void print_incrementing_number_wrapper();
void set_counter(uint32_t new_counter);
uint32_t get_counter();
And in your .c
file, include and use the wrapper header:
#include "static_extern_function_include__module_wrapper.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
int main()
{
printf("Hello World.\n");
print_incrementing_number_wrapper();
printf(" counter = %u\n", get_counter());
print_incrementing_number_wrapper();
printf(" counter = %u\n", get_counter());
print_incrementing_number_wrapper();
printf(" counter = %u\n", get_counter());
return 0;
}
Add non-static
pointers to their functions and variables in the bottom of their .c
file:
Instead of adding wrapper functions and setters/getters, you can use non-static pointer variables instead:
static_extern_function_include__module.c
:
static uint32_t counter = 0;
static void print_incrementing_number()
{
printf("counter = %u\n", counter);
counter++;
}
void foo()
{
print_incrementing_number();
}
typedef void (*void_void_func_ptr_t)(void);
void_void_func_ptr_t print_incrementing_number_ptr =
print_incrementing_number;
uint32_t *counter_ptr = &counter;
Now, forward declare these extern
pointer variables in your .c
file, or make a header file like this:
static_extern_function_include__module_ptrs.h
:
#pragma once
#include <stdint.h>
typedef void (*void_void_func_ptr_t)(void);
extern void_void_func_ptr_t print_incrementing_number_ptr;
extern uint32_t *counter_ptr;
And in your .c
file, include and use the pointer header:
#include "static_extern_function_include__module_ptrs.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
int main()
{
printf("Hello World.\n");
print_incrementing_number_ptr();
printf(" counter = %u\n", *counter_ptr);
print_incrementing_number_ptr();
printf(" counter = %u\n", *counter_ptr);
print_incrementing_number_ptr();
printf(" counter = %u\n", *counter_ptr);
return 0;
}
[Least intrusive / best to minimize changes to someone else's library files] #include
your custom .h
or .c
file in the bottom of their .c
file
If you want to keep the bottom of their .c
file as clean as possible, then you can do that by adding a single #include
statement at the bottom of their .c
file, like this. Note that the extension doesn't really matter. You can use any extension you want. Here, I use _extension.c
to indicate I am extending this module with my own customizations:
#include "static_extern_function_include__module_extensions.c"
Now, put the necessary wrappers or pointers from the examples above into this extension file included above. Here, I'll use the wrapper functions and setters/getters from the 2nd example above:
static_extern_function_include__module_extensions.c
:
void print_incrementing_number_wrapper()
{
print_incrementing_number();
}
void set_counter(uint32_t new_counter)
{
counter = new_counter;
}
uint32_t get_counter()
{
return counter;
}
Now in your main .c
file, just do what you did for the 2nd example above: include and use the wrapper header again, like normal, like this:
#include "static_extern_function_include__module_wrapper.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
int main()
{
printf("Hello World.\n");
print_incrementing_number_wrapper();
printf(" counter = %u\n", get_counter());
print_incrementing_number_wrapper();
printf(" counter = %u\n", get_counter());
print_incrementing_number_wrapper();
printf(" counter = %u\n", get_counter());
return 0;
}
Note: as of later versions of FreeRTOS, this is now possible! They have even provided "hooks" to do this right inside their main library code! See here for example, in V10.6.1 in the bottom of tasks.c
: https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/V10.6.1/tasks.c#L5514-L5534:
#ifdef FREERTOS_MODULE_TEST
#include "tasks_test_access_functions.h"
#endif
#if ( configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1 )
#include "freertos_tasks_c_additions.h"
#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
static void freertos_tasks_c_additions_init( void )
{
FREERTOS_TASKS_C_ADDITIONS_INIT();
}
#endif
#endif
I'll be using that now. Older versions of FreeRTOS did not have this. Search the FreeRTOS code also for configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H
. And, see this forum discussion: https://www.freertos.org/FreeRTOS_Support_Forum_Archive/January_2019/freertos_Retrieve_the_size_and_maximum_usage_of_the_stack_per_task_7ab5c6eb05j.html.
FreeRTOS is doing exactly what I am explaining just above. If you do #define configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H 1
in your custom FreeRTOSConfig.h
file, then they are including your custom freertos_tasks_c_additions.h
file at the bottom of their private tasks.c
file. This way, your file gets access to all of the private, static
functions and variables inside the kernel's tasks.c
, thereby allowing you to inject your customizations into the FreeRTOS kernel without editing any kernel source code files. Brilliant!
See also my detailed answer here: FreeRTOS: obtain the stack size (usStackDepth
) value in words or bytes after calling xTaskCreate()
.
[My preference for unit testing private static
C code] #include
the entire .c
file of interest in the top of your .c
file:
To unit test or get access to a ton of private static
functions and variables from a .c
file, just include the .c
file near the top of your .c
file.
The pre-processor will then copy/paste that entire included file into the top of your file. Imagine that has happened, and write your code from there.
You can access all of the private static
data in your .c
file because it is in your .c
file now.
I won't show an explicit example, for brevity, but you get the point.
2. 替代方案:
以下是一些其他可供考虑的替代方案:
将static
代码复制到您自己的函数中,放在您自己的文件中。
例如:只需复制粘贴即可。然而,我通常不建议这样做,因为维护重复的代码是一种浪费时间的可怕行为,而且容易出现不同步的情况。
如果函数只有3行且只有一个函数,那么可以这样做。但如果有4行或2个函数,最好选择上述其他选项之一,以避免重复的代码。
向他们的库提交一个拉取请求,将感兴趣的函数或变量设置为非static
。
虽然这是一个很好的主意,但要注意,等待上游开发人员接受您的拉取请求可能需要几周、几个月甚至几年,或者永远也不会接受。因此,在此期间,您可以选择上述选项之一来完成自己的工作。
编写一个Bash或Python脚本,在编译时自动从他们的.c
文件中删除static
关键字,从而应用一个编译时补丁。
这实际上是一个非常有效的解决方案,有时也是最佳选择。例如,您可以:
- 手动进行必要的更改,然后生成一个类似于
git diff
的.patch
文件,在构建时应用该文件,或者
- 编写一个自定义的查找和替换脚本,为您执行一些简单的更改,例如从您需要访问的变量和函数中删除
static
。
如果选择后一种选项,我建议您的构建系统首先复制该文件,并让构建脚本修改这个未跟踪的副本。这样,您的原始文件不会被修改,每次构建时git status
和git diff
也不会变得“脏”(有未提交的更改)。
另请参阅
什么时候在C++中使用extern
?
关于"翻译单元"是什么的评论:为什么静态变量对其他文件可见?
即预处理后的源文件(包括所有已包含的文件)。- HolyBlackCat
还有:
我还建议你了解翻译单元,这是构建C++代码的核心概念。编译器只能处理翻译单元,它基本上是一个包含所有已包含的头文件(或源文件,在你的情况下)的单个源文件。- Some programmer dude
我的回答:FreeRTOS:在调用xTaskCreate()
后获取堆栈大小(usStackDepth
)的值(以字或字节为单位)。
static void bt_le_start_notification(void);
如果你打算从另一个C 文件
调用它,为什么要使用static
? - dxiv