C适当使用全局变量[关闭]

Posted

技术标签:

【中文标题】C适当使用全局变量[关闭]【英文标题】:C Appropriate Use of Global Vars [closed] 【发布时间】:2012-10-24 05:13:14 【问题描述】:

我正在用 C 语言构建一个 minishell,并且遇到了一个障碍,似乎可以通过使用全局变量(准确地说是 3)轻松修复它。我认为全局变量是必要的原因是,替代方法是将这些变量传递给我程序中的几乎每个函数。

变量是 mainargc、mainargv 和 shiftMArgV。前两个分别是参数的数量和传递给 main 的参数列表。变量shiftedMArgV 是参数列表,但它可能已被移位。我正在尝试创建内置函数 shift 和 unshift,并使 shiftMArgV 指向不同的参数。

那么,让这些全球化是愚蠢的吗?否则我将不得不修改大量代码,而且我不确定将它们设为全局是否会丢失任何东西。

如果我确实将它们设为全局,那么从主头文件中这样做会很愚蠢吗?

感谢大家的帮助,如果您需要任何澄清,请尽管询问。

【问题讨论】:

“C 适当使用全局变量” - 我会说不存在。 @H2CO3:举例反驳——stdoutstdinstderr @JonathanLeffler 好吧,我也想讨论一下。 (我从不喜欢使用这些全局变量。当我使用它们时,我真的觉得程序的完整性丢失了。) Optimus,出于兴趣,为什么这些值被如此广泛地使用? @JonathanLeffler:stdoutstdinstderr 不是全局变量。它们甚至不是左值。它们是扩展为 FILE * 类型表达式的宏。 【参考方案1】:

作为全局变量的替代方案,考虑“全局函数”:

extern int    msh_mainArgC(void);
extern char **msh_mainArgV(void);
extern char **msh_shiftedArgV(void);

这些函数的实现是微不足道的,但它允许您控制对内存的访问。如果你需要做一些花哨的事情,你可以改变函数的实现。 (我选择将 C 和 V 大写以使差异更加明显;当 8-12 字母名称的最后一个字符不同时,很难发现差异。)

会有一个定义这些函数的实现文件。在该文件中,将有存储相关信息的静态变量,以及用于设置和以其他方式操作变量的函数。原则上,如果您使用足够多的 const 限定符,您可以确保调用代码不能修改数据,除非通过设计用于这样做的函数(或使用强制转换来删除 const-ness)。

这对你是否值得值得商榷。但它可能是。它是一种更接近“面向对象”的操作方式。它是一种考虑然后丢弃的替代方法,而不是不考虑的东西。

请注意,使用这些函数的子系统可能有一个函数来收集全局值,然后将这些值传递给它的下级函数。这使下属不必知道值的来源;只需正确操作即可。如果有全局变量,你必须担心别名——是一个函数传递值(全局变量的副本),但它是否也访问全局变量。有了这些功能,您就不必担心同样的问题了。

【讨论】:

一个好主意,乔纳森。虽然我没有立即理解为什么这比“打足够的const 限定符”更可取。特别是如果这不能取代该行为。不过,也许这里的情况并不是这种方法的最佳范例…… “使用足够多的const 限定符”是指您可能不希望调用这些函数的代码修改参数列表,无论是字符串还是指向这些字符串的指针。因此,您可以尝试char const * const *msh_mainArgV(void); 或类似的东西。当我这样做的时候,我通常会遇到某种问题——我最终无法将我拥有的值传递给我认为我应该能够将它传递给的函数。所以,在添加这些限定符之前我会非常小心,尽管知道调用函数不能破坏它们会很好。【参考方案2】:

我会说这并不愚蠢,但你应该谨慎行事。

通常避免使用全局变量的原因不是它们永远不应该被使用,而是它们的使用导致人们经常导致程序员崩溃和烧毁。通过经验,人们会知道什么时候是正确的时间,什么时候是错误的时间。

如果您已经深入思考过您正在尝试解决的问题,并考虑过您为解决此问题而编写的代码,并考虑过此代码的未来(即您是否会损害可维护性)并且觉得全局是不可避免的或者更好地代表编码的解决方案,那么你应该使用全局。

以后,您可能会崩溃和燃烧,但这种经验将帮助您以后辨别什么是更好的选择。相反,如果您觉得不使用全局变量可能会导致崩溃和烧毁,那么您之前的经验就是说您应该使用它们。你应该相信这种直觉。

Dijkstra 在a paper 中讨论了goto 语句可能造成的危害,但在我看来,他的讨论也解释了我们在使用全局变量时遇到的一些困难。可能值得一读。

This answer 和 this answer 也可能有用。

【讨论】:

谢谢,我已经考虑了好几个小时,打算试一试。我有一堆函数指针指向某种类型的函数。这些函数实例之一现在需要 mainargc、mainargv 和 shiftMArgV。当它们不需要这些变量时,我不想更改其余函数的原型。这些变量用于我的所有文件(大约 6 个),所以对我来说很有意义。感谢您的建议。 在这里将它们视为全局变量听起来很明智。如果您只从全局变量中读取而不给它们写入,那么全局变量会更安全,因为如果程序的任何部分会影响其他所有部分,那么围绕全局变量的恐惧源于随着时间的推移难以跟踪程序的状态。 顺便提一下,@Optimus_Pwn,如果您还没有阅读 Dijkstra 的论文 Gotos considered harmful,可能值得一读。他围绕 goto 危险的许多 cmets 论点也适用于全局变量。【参考方案3】:

全局变量是可以的,只要它们在逻辑上是真正的全局变量,而不仅仅是让您的生活更轻松的手段。例如,全局变量可以描述您的程序在其中执行的环境,或者换句话说,可以描述与您的应用的系统级别相关的属性。

【讨论】:

啊,好吧。我真的认为在这种情况下全局变量会是一个更优雅的解决方案,因为否则我将不得不将这些变量传递给许多甚至不会使用它们的函数。【参考方案4】:

我曾经使用过的几乎所有复杂软件都有一组定义良好的全局变量。这没什么不好。它们的范围从少数到大约十几个。在后一种情况下,它们通常按逻辑分组在结构中。

全局变量通常作为外部变量在头文件中公开,然后在源文件中定义。请记住,全局变量在线程之间共享,因此必须受到保护,除非使用线程本地存储声明它们更有意义。

【讨论】:

我想我会尝试实现全局变量。如果我将全局变量放在头文件中,我应该只在其中一个源文件中定义全局变量,对吗?感谢您的建议。 @Optimus_Pwn 是的,它们只需要定义一次。无论如何都不可能这样做,因为链接器在看到它们在多个目标文件中定义时会叫。 我在我的 msh.c 文件(主源文件)中定义了一次。但是,当我尝试在其他文件(expand.c)中使用它们时,它会显示 expand.c:(.text+0x27c): undefined reference to `m_argc' 在你的头文件中你将它们声明为extern。在源文件中省略extern,因为您想在那里实际定义它们,而不仅仅是声明它们。 extern 关键字类似于前向声明;它只是告诉编译器变量,它不会创建它。还要确保变量不是static,因为这会阻止它们被导出,从而阻止链接器看到它们。 我明白了,这是因为我试图在 main 内部而不是在 main 之上定义它们。我现在可以很好地使用全局变量了,非常感谢。【参考方案5】:

对于一个 shell,你有比这更多的状态。您拥有重新映射的文件描述符的状态(出于各种原因需要对其进行跟踪)、trap 配置、set 选项、环境和 shell 变量...

一般来说,全局变量是错误的解决方案。相反,所有状态都应该保存在某种上下文结构中,一个指向它的指针到处传递。这是一个很好的程序设计,通常它允许您在同一个进程中运行相同代码的多个实例(例如多个解释器、多个视频解码器等)。

话虽如此,shell 是一个非常特殊的情况,因为它还处理 很多 无法保存在结构中的全局状态:信号处置、文件描述符和映射、子进程、进程组、控制终端等等比编写传统的 shell 更困难的任务。因此,我可能会给自己一些余地来使用全局变量“懒惰地”编写你的 shell。但是,如果这是一个学习练习,请尝试在每次引入全局变量或状态时仔细识别,为什么要这样做,以及如何在没有全局状态的情况下以不同的方式实现程序。这对您将来非常有用。

【讨论】:

以上是关于C适当使用全局变量[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

适当的全局变量

全局变量不会在C中的线程更新[关闭]

为啥全局变量是邪恶的? [关闭]

C语言全局变量(c文件和h文件中的全局变量静态全局变量)使用注意事项

C语言全局变量(c文件和h文件中的全局变量静态全局变量)使用注意事项

C语言所有变量都默认为auto,全局变量不能为auto,那全局变量默认为啥?