4

这里的软件经验很少的新 EE。在过去的几年里,在这个网站上阅读了很多问题,这将是我的第一个问题/帖子。还没有完全找到这个问题的答案。

我想知道让函数修改主体内的全局变量(不将其作为参数传递)与传递变量地址之间的区别/动机。

这是每个示例以使其更清楚。假设我声明了一些函数“peripheral.c”(在“peripheral.h”中使用它们的正确原型,并在“implementation.c”中使用它们

方法一:

//peripheral.c

//macros, includes, etc

void function(*x){
   //modify x
}

.

//implementation.c

#include "peripheral.h"

static uint8 var;

function(&var);  //this will end up modifying var

方法二:

//peripheral.c

//macros, includes, etc

void function(void){
   //modify x
}

.

//implementation.c

#include "peripheral.h"

static uint8 x;

function();    //this will modify x

避免使用“全局”变量的唯一动机是什么?(另外,如果它只有文件范围,它真的是全球性的吗?)

希望这个问题是有道理的。谢谢

4

6 回答 6

5

接收指向变量的参数的函数更通用。它可用于修改全局变量、局部变量或任何变量。修改全局的函数可以执行该任务并且只能执行该任务。

哪个是首选完全取决于上下文。有时一种方法更好,有时另一种方法更好。不可能明确地说一种方法总是比另一种更好。

至于你的全局变量是否真的是全局的,它是全局的,因为你的进程中只有一个该变量的实例。

于 2012-06-04T19:42:36.673 回答
2

static变量有内部链接,它们不能在它们所在 的翻译单元之外被访问。

因此,如果您想static在另一个 TU 中修改全局变量,则必须像第一个示例一样通过函数参数将其作为指针传递。

您的第二个示例无法工作,因为x无法在外部访问implementation.c,它应该会给您一个编译错误。

好读:
什么是外联和内联?

于 2012-06-04T19:38:54.280 回答
1

首先,在 C/C++ 中,“全局”确实意味着文件范围(尽管如果您在标头中声明全局,那么它会包含在 #include 该标头的文件中)。

当调用函数具有被调用函数应修改的某些数据时,例如在您的示例中,使用指针作为参数很有用。当正在修改其输入的函数不确切知道它正在修改什么时,作为参数的指针特别有用。例如:

scanf("%d", &foo);

scanf 不会知道任何关于 foo 的信息,并且你不能修改它的源代码来让它知道 foo。但是,scanf 使用指向变量的指针,这允许它修改任意变量的值(当然是它支持的类型)。这使得它比依赖全局变量的东西更可重用。

在您的代码中,您通常应该更喜欢使用指向变量的指针。但是,如果您注意到您正在将相同的信息块传递给许多函数,那么全局变量可能是有意义的。也就是说,你应该更喜欢

int g_state;
int foo(int x, int y);
int bar(int x, int y);
void foobar(void);
...

int foo(int x, int y, int state);
int bar(int x, int y, int state);
void foobar(int state);
...

基本上,将全局变量用于应该由它们所在的文件(或文件,如果您在标头中声明全局)中的所有内容共享的值。使用指针作为值的参数,这些值应该在一组较小的函数之间传递以进行共享,以及可能有多个变量您希望对其执行相同操作的情况。

编辑:另外,作为未来的说明,当您说“指向函数的指针”时,人们会假设您的意思是指向函数的指针,而不是将指针作为参数传递给函数。“指针作为参数”对于您在这里的要求更有意义。

于 2012-06-04T19:49:32.230 回答
0

全局变量的主要问题是它们促进了函数或模块之间的所谓“紧密耦合” 。在您的第二个设计中,peripheral模块知道并依赖于 的设计implementation,以至于如果您implementation通过删除或重命名进行更改,即使不触及任何代码x,您也会破坏。 您还无法独立于模块 peripheral重用。peripheralimplementation

同样,这种设计意味着functioninperipheral只能处理 的单个实例x,无论其x代表什么。

理想情况下,函数及其调用者应仅通过参数、返回值和异常(在适当的情况下)进行通信。如果您需要在调用之间维护状态,请使用可写参数来存储该状态,而不是依赖全局。

于 2012-06-04T20:23:43.807 回答
0

这里有几个不同的问题:

  1. 一般来说,“全局变量不好”。如果可以避免,请不要使用它们。是的,最好传递一个指向变量的指针,以便函数可以修改它,而不是使其成为全局变量,以便函数可以隐式修改它。

  2. 话虽如此,全局变量还是很有用的:一定要酌情使用它们

  3. 是的,“全局”可以表示“函数之间”(在一个模块内)以及“模块之间”(在整个程序中是全局的)。

  4. 关于您的代码,有几件有趣的事情需要注意:

    a) 大多数变量是从“堆栈”分配的。当您在这样的函数之外声明一个变量时,它是从“块存储”分配的 - 空间存在于程序的生命周期中。

    b)当您将其声明为“静态”时,您将其从其他模块“隐藏”:该名称在模块之外不可见。

    c) 如果你想要一个真正的全局变量,你不会使用关键字“static”。您可以在头文件中将其声明为“extern uint8 var”(因此所有模块都有定义)。

于 2012-06-04T19:45:20.293 回答
0

我不确定您的第二个示例是否真的有效,因为您声明x为静态(因此将其范围限制为文件),但除此之外,指针传递版本还有一些优点:

  • 它为您在分配和模块化方面提供了更大的灵活性。虽然您只能在文件中拥有一个全局变量的副本,但您可以拥有任意数量的指针,它们可以指向在许多不同位置创建的对象(静态数组、malloc、堆栈变量......)

  • 全局变量被强制到每个函数中,因此您必须始终意识到有人可能想要修改它们。另一方面,指针只能由您明确传递给它们的函数访问。

  • 除了最后一点之外,全局变量都使用相同的范围,并且可能会因变量过多而变得混乱。另一方面,指针与普通变量一样具有词法作用域,并且它们的作用域受到更多限制。


是的,如果你有一个小的、独立的文件,事情会变得有些模糊。如果您不打算实例化一个以上的“对象”,那么有时静态全局变量(对于单个文件是本地的)与指向结构的指针一样工作。

于 2012-06-04T19:46:05.167 回答