如何在两个函数之间共享变量而不将其声明为C语言中的全局变量

时间:2021-03-20 16:49:27

I've been reading through a lot of answers, and there are a lot of opinions about this but I wasn't able to find a code that answers my question (I found a lot of code that answers "how to share variables by declaring")

我一直在阅读很多答案,对此有很多意见,但我找不到能够回答我问题的代码(我发现很多代码都回答“如何通过声明来共享变量” “)

Here's the situation:

这是情况:

  • Working with embedded systems
  • 使用嵌入式系统

  • Using IAR workbench systems
  • 使用IAR工作台系统

  • STM32F4xx HAL drivers
  • STM32F4xx HAL驱动程序

  • Declaring global variables is not an option (Edit: Something to do with keeping the memory size small, so local variables disappear at the end of scope but the global variable stay around. The local variables were sent out as outputs, so we discard them right away as we don't need them)
  • 声明全局变量不是一个选项(编辑:与保持内存大小有关的事情,因此局部变量在范围的末尾消失但全局变量保持不变。局部变量作为输出发送出去,所以我们将它们丢弃因为我们不需要他们)

  • C language
  • in case this is important: 2 .c files, and 1 .h is included in both
  • 如果这很重要:2 .c文件和1 .h都包括在内

Now that's out of the way, let me write an example.

现在已经不在了,让我写一个例子。

file1.c - Monitoring

file1.c - 监控

void function(){
    uint8_t varFlag[10]; // 10 devices
    for (uint8_t i = 0; i < 10; i++)
    {
        while (timeout <= 0){
            varFlag[i] = 1;
            // wait for response. We'll know by the ack() function
            // if response back from RX, 
            // then varFlag[i] = 0;
    }

file2.c - RX side

file2.c - RX端

// listening... once indicated, this function is called
// ack is not called in function(), it is called when 
// there's notification that there is a received message
// (otherwise I would be able to use a pointer to change 
// the value of varFlag[]
void ack(uint8_t indexDevice)
{
    // indexDevice = which device was acknowledged? we have 10 devices
    // goal here is to somehow do varFlag[indexDevice] = 0
    // where varFlag[] is declared in the function()

}

3 个解决方案

#1


3  

You share values or data, not variables. Stricto sensu, variables do not exist at runtime; only the compiler knows them (at most, with -g, it might put some metadata such as offset & type of locals in the debugging section -which is usually stripped in production code- of the executable). Ther linker symbol table (for global variables) can, and often is, stripped in a embedded released ELF binary. At runtime you have some data segment, and probably a call stack made of call frames (which hold some local variables, i.e. their values, in some slots). At runtime only locations are relevant.

您可以共享值或数据,而不是变量。 Stricto sensu,变量在运行时不存在;只有编译器知道它们(最多使用-g,它可能会在调试部分中放置一些元数据,例如偏移量和本地类型 - 通常在可执行文件的生产代码中被删除)。 Ther链接器符号表(用于全局变量)可以并且通常在嵌入式发布的ELF二进制文件中被剥离。在运行时,您有一些数据段,可能是一个由调用帧组成的调用堆栈(在某些插槽中保存一些局部变量,即它们的值)。在运行时,只有位置是相关的。

(some embedded processors have severe restrictions on their call stack; other have limited RAM, or scratchpad memory; so it would be helpful to know what actual processor & ISA you are targeting, and have an idea of how much RAM you have)

(某些嵌入式处理器对其调用堆栈有严格限制;其他嵌入式处理器的RAM或暂存器内存有限;因此了解您所针对的实际处理器和ISA以及了解您拥有多少RAM会很有帮助)

So have some global variables keeping these shared values (perhaps indirectly thru some pointers and data structures), or pass these values (perhaps indirectly, likewise...) thru arguments.

所以有一些全局变量保留这些共享值(可能间接通过一些指针和数据结构),或通过参数传递这些值(可能是间接的,同样......)。

So if you want to share the ten bytes varFlag[10] array:

所以如果你想共享十个字节的varFlag [10]数组:

  • it looks like you don't want to declare uint8_t varFlag[10]; as a global (or static) variable. Are you sure you really should not (these ten bytes have to sit somewhere, and they do consume some RAM anyway, perhaps in your call stack....)?

    看起来你不想声明uint8_t varFlag [10];作为全局(或静态)变量。你确定你真的不应该(这十个字节必须坐在某个地方,而且他们确实消耗了一些RAM,可能在你的调用堆栈中......)?

  • pass the varFlag (array, decayed to pointer when passed as argument) as an argument, so perhaps declare:

    传递varFlag(数组,作为参数传递时衰减到指针)作为参数,所以可能声明:

    void ack(uint8_t indexDevice, uint8_t*flags);
    

    and call ack(3,varFlag) from function...

    并从函数中调用ack(3,varFlag)...

    • or declare a global pointer:

      或声明一个全局指针:

      uint8_t*globflags;
      

and set it (using globflags = varFlag;) at the start of the function declaring varFlag as a local variable, and clear if (using globflags = NULL;) at the end of that function.

并在函数的开头设置它(使用globflags = varFlag;),将varFlag声明为局部变量,并在该函数的末尾清除if(使用globflags = NULL;)。

I would suggest you to look at the assembler code produced by your compiler (with GCC you might compile with gcc -S -Os -fverbose-asm -fstack-usage ....). I also strongly suggest you to get your code reviewed by a colleague...

我建议你看一下你的编译器生成的汇编程序代码(使用GCC你可以使用gcc -S -Os -fverbose-asm -fstack-usage ....编译)。我还强烈建议您让同事审核您的代码......

PS. Perhaps you should use GCC or Clang/LLVM as a cross-compiler, and perhaps your IAR is actually using such a compiler...

PS。也许您应该使用GCC或Clang / LLVM作为交叉编译器,也许您的IAR实际上正在使用这样的编译器......

#2


1  

Put the functions into a seperate translation unit and use a static variable:

将函数放入单独的翻译单元并使用静态变量:

static type var_to_share = ...;

void function() {
    ...
}

void ack() {
    ...
}

Note that I said translation unit, not file. You can do some #include magic (in the cleanest way possible) to keep both function definitions apart.

请注意,我说翻译单位,而不是文件。你可以做一些#include魔术(以最干净的方式)来保持两个功能定义。

#3


1  

Your argument for not using global variables:

您不使用全局变量的论点:

Something to do with keeping the memory size small, so local variables disappear at the end of scope but the global variable stay around. The local variables were sent out as outputs, so we discard them right away as we don't need them

与保持内存大小有关的事情,因此局部变量在范围的末尾消失,但全局变量保持不变。局部变量作为输出发送出去,因此我们立即丢弃它们,因为我们不需要它们

confuses lifetime with scope. Variables with static lifetime occupy memory permanently regardless of scope (or visibility). A variable with global scope happens to also be statically allocated, but then so is any other static variable.

将生命与范围混为一谈。无论范围(或可见性)如何,具有静态生命周期的变量都会永久占用内存。具有全局范围的变量也恰好是静态分配的,但是任何其他静态变量也是如此。

In order to share a variable across contexts it must necessarily be static, so there is no memory saving by avoiding global variables. There are however plenty of other stronger arguments for avoiding global variables and you should read A Pox on Globals by Jack Ganssle.

为了跨上下文共享变量,它必须是静态的,因此通过避免全局变量不会节省内存。然而,有许多其他更强大的论据可以避免全局变量,你应该阅读杰克·甘斯勒的全球化问题。

C supports three-levels of scope:

C支持三级范围:

  • function (inside a function)
  • 功能(在一个功能内)

  • translation-unit (static linkage, outside a function)
  • 翻译单元(静态链接,函数外)

  • global (external linkage)
  • 全球(外部联动)

The second of these allows variable to be directly visible amongst functions in the same source file, while external linkage allows direct visibility between multiple source files. However you want to avoid direct access in most cases since that is the root of the fundamental problem with global variables. You can do this using accessor functions; to use your example you might add a file3.c containing:

其中第二个允许变量在同一源文件中的函数之间直接可见,而外部链接允许在多个源文件之间直接可见。但是,在大多数情况下,您希望避免直接访问,因为这是全局变量的基本问题的根源。您可以使用访问器功能执行此操作;要使用您的示例,您可以添加包含以下内容的file3.c:

#include "file3.h"

static uint8_t varFlag[10]; 
void setFlag( size_t n )
{
    if( n < sizeof(varFlag) )
    {
        varFlag[n] = 1 ;
    }
}

void clrFlag( size_t n )
{
    if( n < sizeof(varFlag) )
    {
        varFlag[n] = 0 ;
    }
}

uint8_t getFlag( size_t n )
{
    return varFlag[n] == 0 ? 0 : 1 ;
}

With an associated header file3.h

使用关联的头文件3.h

#if !defined FILE3_INCLUDE
#define FILE3_INCLUDE

void setFlag( size_t n ) ;
void clrFlag( size_t n ) ;
uint8_t getFlag( size_t n ) ;

#endif

which file1.c and file2.c include so they can access varFlag[] via the accessor functions. The benefits include:

file1.c和file2.c包含哪些内容,因此可以通过访问器函数访问varFlag []。好处包括:

  • varFlag[] is not directly accessible
  • varFlag []无法直接访问

  • the functions can enforce valid values
  • 这些函数可以强制执行有效值

  • in a debugger you can set a breakpoint catch specifically set, clear or read access form anywhere in the code.
  • 在调试器中,您可以在代码中的任何位置设置专门设置,清除或读取访问表单的断点捕获。

  • the internal data representation is hidden
  • 内部数据表示是隐藏的

Critically the avoidance of a global variable does not save you memory - the data is still statically allocated - because you cannot get something for nothing varFlag[] has to exist, even if it is not visible. That said, the last point about internal representation does provide a potential for storage efficiency, because you could change your flag representation from uint8_t to single bit-flags without having to change interface to the data or the accessing the accessing code:

关键是避免使用全局变量并不能节省内存 - 数据仍然是静态分配的 - 因为即使不可见,你也无法获得varFlag []必须存在的东西。也就是说,关于内部表示的最后一点确实提供了存储效率的潜力,因为您可以将标志表示从uint8_t更改为单个位标志,而无需更改数据接口或访问访问代码:

#include <limits.h>
#include "file3.h"

static uint16_t varFlags ;

void setFlag( size_t n )
{
    if( n < sizeof(varFlags) * CHAR_BIT )
    {
        varFlags |= 0x0001 << n ;
    }
}

void clrFlag( size_t n )
{
    if( n < sizeof(varFlags) * CHAR_BIT )
    {
        varFlags &= ~(0x0001 << n) ;
    }
}

uint8_t getFlag( size_t n )
{
    return (varFlags & (0x0001 << n)) == 0 ? 0 : 1 ;
}

There are further opportunities to produce robust code, for example you might make only the read accessor (getter) publicly visible and hide the so that all but one translation unit has read-only access.

还有更多机会生成健壮的代码,例如,您可能只将读取访问器(getter)公开显示并隐藏,以便除一个翻译单元之外的所有单元都具有只读访问权限。

#1


3  

You share values or data, not variables. Stricto sensu, variables do not exist at runtime; only the compiler knows them (at most, with -g, it might put some metadata such as offset & type of locals in the debugging section -which is usually stripped in production code- of the executable). Ther linker symbol table (for global variables) can, and often is, stripped in a embedded released ELF binary. At runtime you have some data segment, and probably a call stack made of call frames (which hold some local variables, i.e. their values, in some slots). At runtime only locations are relevant.

您可以共享值或数据,而不是变量。 Stricto sensu,变量在运行时不存在;只有编译器知道它们(最多使用-g,它可能会在调试部分中放置一些元数据,例如偏移量和本地类型 - 通常在可执行文件的生产代码中被删除)。 Ther链接器符号表(用于全局变量)可以并且通常在嵌入式发布的ELF二进制文件中被剥离。在运行时,您有一些数据段,可能是一个由调用帧组成的调用堆栈(在某些插槽中保存一些局部变量,即它们的值)。在运行时,只有位置是相关的。

(some embedded processors have severe restrictions on their call stack; other have limited RAM, or scratchpad memory; so it would be helpful to know what actual processor & ISA you are targeting, and have an idea of how much RAM you have)

(某些嵌入式处理器对其调用堆栈有严格限制;其他嵌入式处理器的RAM或暂存器内存有限;因此了解您所针对的实际处理器和ISA以及了解您拥有多少RAM会很有帮助)

So have some global variables keeping these shared values (perhaps indirectly thru some pointers and data structures), or pass these values (perhaps indirectly, likewise...) thru arguments.

所以有一些全局变量保留这些共享值(可能间接通过一些指针和数据结构),或通过参数传递这些值(可能是间接的,同样......)。

So if you want to share the ten bytes varFlag[10] array:

所以如果你想共享十个字节的varFlag [10]数组:

  • it looks like you don't want to declare uint8_t varFlag[10]; as a global (or static) variable. Are you sure you really should not (these ten bytes have to sit somewhere, and they do consume some RAM anyway, perhaps in your call stack....)?

    看起来你不想声明uint8_t varFlag [10];作为全局(或静态)变量。你确定你真的不应该(这十个字节必须坐在某个地方,而且他们确实消耗了一些RAM,可能在你的调用堆栈中......)?

  • pass the varFlag (array, decayed to pointer when passed as argument) as an argument, so perhaps declare:

    传递varFlag(数组,作为参数传递时衰减到指针)作为参数,所以可能声明:

    void ack(uint8_t indexDevice, uint8_t*flags);
    

    and call ack(3,varFlag) from function...

    并从函数中调用ack(3,varFlag)...

    • or declare a global pointer:

      或声明一个全局指针:

      uint8_t*globflags;
      

and set it (using globflags = varFlag;) at the start of the function declaring varFlag as a local variable, and clear if (using globflags = NULL;) at the end of that function.

并在函数的开头设置它(使用globflags = varFlag;),将varFlag声明为局部变量,并在该函数的末尾清除if(使用globflags = NULL;)。

I would suggest you to look at the assembler code produced by your compiler (with GCC you might compile with gcc -S -Os -fverbose-asm -fstack-usage ....). I also strongly suggest you to get your code reviewed by a colleague...

我建议你看一下你的编译器生成的汇编程序代码(使用GCC你可以使用gcc -S -Os -fverbose-asm -fstack-usage ....编译)。我还强烈建议您让同事审核您的代码......

PS. Perhaps you should use GCC or Clang/LLVM as a cross-compiler, and perhaps your IAR is actually using such a compiler...

PS。也许您应该使用GCC或Clang / LLVM作为交叉编译器,也许您的IAR实际上正在使用这样的编译器......

#2


1  

Put the functions into a seperate translation unit and use a static variable:

将函数放入单独的翻译单元并使用静态变量:

static type var_to_share = ...;

void function() {
    ...
}

void ack() {
    ...
}

Note that I said translation unit, not file. You can do some #include magic (in the cleanest way possible) to keep both function definitions apart.

请注意,我说翻译单位,而不是文件。你可以做一些#include魔术(以最干净的方式)来保持两个功能定义。

#3


1  

Your argument for not using global variables:

您不使用全局变量的论点:

Something to do with keeping the memory size small, so local variables disappear at the end of scope but the global variable stay around. The local variables were sent out as outputs, so we discard them right away as we don't need them

与保持内存大小有关的事情,因此局部变量在范围的末尾消失,但全局变量保持不变。局部变量作为输出发送出去,因此我们立即丢弃它们,因为我们不需要它们

confuses lifetime with scope. Variables with static lifetime occupy memory permanently regardless of scope (or visibility). A variable with global scope happens to also be statically allocated, but then so is any other static variable.

将生命与范围混为一谈。无论范围(或可见性)如何,具有静态生命周期的变量都会永久占用内存。具有全局范围的变量也恰好是静态分配的,但是任何其他静态变量也是如此。

In order to share a variable across contexts it must necessarily be static, so there is no memory saving by avoiding global variables. There are however plenty of other stronger arguments for avoiding global variables and you should read A Pox on Globals by Jack Ganssle.

为了跨上下文共享变量,它必须是静态的,因此通过避免全局变量不会节省内存。然而,有许多其他更强大的论据可以避免全局变量,你应该阅读杰克·甘斯勒的全球化问题。

C supports three-levels of scope:

C支持三级范围:

  • function (inside a function)
  • 功能(在一个功能内)

  • translation-unit (static linkage, outside a function)
  • 翻译单元(静态链接,函数外)

  • global (external linkage)
  • 全球(外部联动)

The second of these allows variable to be directly visible amongst functions in the same source file, while external linkage allows direct visibility between multiple source files. However you want to avoid direct access in most cases since that is the root of the fundamental problem with global variables. You can do this using accessor functions; to use your example you might add a file3.c containing:

其中第二个允许变量在同一源文件中的函数之间直接可见,而外部链接允许在多个源文件之间直接可见。但是,在大多数情况下,您希望避免直接访问,因为这是全局变量的基本问题的根源。您可以使用访问器功能执行此操作;要使用您的示例,您可以添加包含以下内容的file3.c:

#include "file3.h"

static uint8_t varFlag[10]; 
void setFlag( size_t n )
{
    if( n < sizeof(varFlag) )
    {
        varFlag[n] = 1 ;
    }
}

void clrFlag( size_t n )
{
    if( n < sizeof(varFlag) )
    {
        varFlag[n] = 0 ;
    }
}

uint8_t getFlag( size_t n )
{
    return varFlag[n] == 0 ? 0 : 1 ;
}

With an associated header file3.h

使用关联的头文件3.h

#if !defined FILE3_INCLUDE
#define FILE3_INCLUDE

void setFlag( size_t n ) ;
void clrFlag( size_t n ) ;
uint8_t getFlag( size_t n ) ;

#endif

which file1.c and file2.c include so they can access varFlag[] via the accessor functions. The benefits include:

file1.c和file2.c包含哪些内容,因此可以通过访问器函数访问varFlag []。好处包括:

  • varFlag[] is not directly accessible
  • varFlag []无法直接访问

  • the functions can enforce valid values
  • 这些函数可以强制执行有效值

  • in a debugger you can set a breakpoint catch specifically set, clear or read access form anywhere in the code.
  • 在调试器中,您可以在代码中的任何位置设置专门设置,清除或读取访问表单的断点捕获。

  • the internal data representation is hidden
  • 内部数据表示是隐藏的

Critically the avoidance of a global variable does not save you memory - the data is still statically allocated - because you cannot get something for nothing varFlag[] has to exist, even if it is not visible. That said, the last point about internal representation does provide a potential for storage efficiency, because you could change your flag representation from uint8_t to single bit-flags without having to change interface to the data or the accessing the accessing code:

关键是避免使用全局变量并不能节省内存 - 数据仍然是静态分配的 - 因为即使不可见,你也无法获得varFlag []必须存在的东西。也就是说,关于内部表示的最后一点确实提供了存储效率的潜力,因为您可以将标志表示从uint8_t更改为单个位标志,而无需更改数据接口或访问访问代码:

#include <limits.h>
#include "file3.h"

static uint16_t varFlags ;

void setFlag( size_t n )
{
    if( n < sizeof(varFlags) * CHAR_BIT )
    {
        varFlags |= 0x0001 << n ;
    }
}

void clrFlag( size_t n )
{
    if( n < sizeof(varFlags) * CHAR_BIT )
    {
        varFlags &= ~(0x0001 << n) ;
    }
}

uint8_t getFlag( size_t n )
{
    return (varFlags & (0x0001 << n)) == 0 ? 0 : 1 ;
}

There are further opportunities to produce robust code, for example you might make only the read accessor (getter) publicly visible and hide the so that all but one translation unit has read-only access.

还有更多机会生成健壮的代码,例如,您可能只将读取访问器(getter)公开显示并隐藏,以便除一个翻译单元之外的所有单元都具有只读访问权限。