关于 c :全局和”单一”的”事物”的全局变量?

global variables for "things" that are global and "single"?

在处理微控制器时,有些东西本质上是全局性的——我正在考虑诸如串行端口或其他接口之类的外围设备。还有一些外设不仅是全局的,而且只有一个(而且永远不会更多)——比如控制核心时钟或中断控制器的外设。这些外设确实具有某种全局状态(例如 - 核心时钟设置为某个值),并且反向计算这些值是低效的。

如果我希望我的程序很好地面向对象,我将很难决定如何处理这些对象......全局变量并不好,这很明显,但我只是不\\不知道(没有足够的经验)我是否应该尝试"隐藏"这些东西是全局的事实......例如"cin"或"stdout"也是全局变量(让我们忽略事实上,在多线程应用程序中,这些通常是线程特定的)并且没有人隐藏这一点......让我们坚持使用时钟生成器外围设备 - 只有一个,所以我可以使用单例反模式(; 或制作类是静态的,或者只有一个对象是全局的(这就是我通常所做的),因为这个对象存储了当前的时钟设置,并且很多其他事情都需要这个值 - 它需要设置系统RTOS 使用的定时器,需要为其他外设设置时钟(UART 波特率,SPI 比特率,...),需要为外部存储器设置正确的时钟或配置存储器等待状态。 \\这就是为什么我认为在 main() 中创建一个对象并将其传递到各处会有点麻烦...

我可以编写方法,以便所有"全局"信息都来自外围寄存器(例如,内核频率可以从当前 PLL 设置反向计算),但这似乎也是一个错误的想法,而不是提到在任何地方为时钟生成器外围设备创建对象看起来很有趣...

当前时钟设置可以存储在类的静态成员中,但从这里开始,只有一小步迈向完全静态的类(因为 "this" 指针对于没有状态的类将毫无用处)...

通常在非面向对象程序中找到的解决方案最接近于完全静态的类——只有对全局变量进行操作的函数。

任何人都知道如何很好地处理这种情况,或者这个问题是否值得花时间?也许我应该只使用一个全局对象并完成它? (;


If I'd like my program to be nicely object-oriented, I'm having hard time deciding how to deal with such objects... Global variables are not nice and this is obvious, but I just don't know (not enough experience) whether I should try to"hide" the fact that these things ARE global...

当我读到这篇文章时,我想知道你是否知道为什么要使用 OOP 以及为什么不使用全局变量。

首先,OOP 是一个工具,而不是一个目标。在您的情况下,中断控制器不需要派生和虚拟功能之类的东西。您所需要的只是一个对其进行编程的接口,封装在一个类中。你甚至可以使用一组简单的函数来做到这一点(C=风格模块化编程),而不会放弃可维护性。在您的情况下,使单个实例全局化更加清晰。想象一下替代方案,程序的不同部分可以实例化一个用于访问下面相同的 UART 的类。如果您使用全局变量,代码(或者更确切地说是作者)会意识到这一点,并会考虑如何协调访问。

现在,关于全局变量,这里有一个为什么不使用它们的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int a, b, c;

void f1()
{
    c = a;
    f3();
}

void f2()
{
    c = b;
    f3();
}

void f3()
{
    // use c
}

int main()
{
    a = 4;
    f1();
    b = 5;
    f2();
}

这里的重点是参数存储在全局变量中,而不是作为实际参数传递,因此很难看到它们在何时何地被使用。此外,上述使用完全排除了任何递归调用。关于您的环境,有些东西本质上是全局的,因为它们是环境的独特部分,例如中断控制器(类似于 cin/cout/cerr/clog)。不要担心那些。在您需要考虑限制访问之前,必须在各地使用它们中的许多。

有两个指南可以让这更容易:

  • 一个对象的范围越大,它就越需要一个会说话的名字(对比上面的 a、b、c)。在您的情况下,我会将所有特定于硬件的对象存储在一个公共命名空间中,因此很明显某些代码正在访问全局变量。这也允许单独测试和重用这个命名空间。许多硬件 vendor甚至以库的形式提供类似的东西,以帮助应用程序开发。
  • 在封装/建模硬件的代码中,执行请求,但不要做出特定于其之上的应用程序的决策。比较例如标准流,标准库本身提供但从未使用过,除了可能用于在断言失败后中止之前转储致命错误信息。

Singleton 模式无疑是争论的焦点。有些人只是说它"不好",但我不同意;它只是被广泛滥用和误解。它在某些情况下绝对有用。事实上,它完全适用于你所描述的情况。

如果您有一个需要全局可用的类,并且本质上不能有多个实例,那么单例是最好的选择。


您已经大致概述了您的选择:

  • 全局变量
  • 类/单例的静态数据成员

最终由您在两者之间做出决定,并从美学或其他方面选择您更喜欢的。

最终,正如您所说,您仍然拥有一个时钟发生器、一个 UART 等。

要记住的一件事是,仅仅为了抽象的目的而进行过多的抽象不会给你带来太多好处。然而,它可能做的是让不熟悉您的代码的人更难弄清楚事情是如何在类层后面真正工作的。因此,请考虑您的团队(如果有)。


单例、全局对象、静态类,它们都是一样的。用你想要的任何调味品来打扮邪恶的全局状态,它仍然是全局状态。问题在于沙拉,而不是调料,可以这么说。

一个很少探索的路径是一元代码,Haskell 风格(在 C 中是的)。我自己从未尝试过,但从外观上看,这个选项应该很有趣。参见例如这里是 C 中 Monad 接口的示例实现。