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

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

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

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

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

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

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

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

  • 阅读 [Singleton][1] 模式,这就是你应该使用的。 [1]:stackoverflow.com/questions/1008019/c-singleton-design-patte??rn
  • 我不确定你的实际问题是什么。你所描述的正是单例模式的重点,我假设你已经知道了,因为你自己提到了它。
  • 我也知道,现在 Singleton 没有很好的"宣传"(; 并且不受欢迎......此外 - 实际上 Singleton 只是一个单一的全局变量或静态类 - 它仍然是全局的。我正在尝试要学习的是在这种情况下是否可以以某种方式避免"全局性"。
  • @Freddie - 就像你说的那样,单例模式不受欢迎,因为有些人每次需要一个类的实例时都会使用它。我们没有任何模式来拥有两个或三个相同类的对象,那么为什么要用一个对象使情况复杂化呢?如果您需要一个时钟驱动程序,只需创建该类的一个对象并完成它!您可以在需要时传递对它的引用。您很快就会发现它不会在任何地方使用。
  • 如果您有共同的功能,那么即使它们的数量是静态的,它们仍然可以是一个类。例如,某些板具有多个串行端口。一些驱动程序会有组合,例如触摸屏驱动程序可能使用 spi 驱动程序。 Linux 的结构是这样的,但是用 C 编写以获得更多控制。您认为是 static 的部分可能不是;特别是如果您开始查看芯片系列。
  • 我从来没有说过 USART 是"单一的"——这是一个天生具有全球性的例子。一个单一且全局的东西是中断控制器或时钟控制器。
  • @FreddieChopin 抱歉,我所说的并不是您的设计。用 UART 替代中断控制器和时钟控制器。有些板确实有多个中断控制器。我赞成帕克坎普,因为这是他回答的风格。董事会的资源是静态的,没有改变。但是,这并不意味着它对于代码的所有用途都是静态的。这非常适合单件或多件。对象的物理限制。


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 接口的示例实现。

  • -1 通常在 OOP 概念中这是正确的。但是,在这种情况下,对象存在物理限制。如果只有一个中断控制器,为什么能够创建更多的中断控制器会有帮助?当然,最好将您的精力集中在 OOP 概念将得到回报的设计的其他部分。如果您的对象与问题域匹配,OOP 就很棒。这里有一个物理限制。
  • @artlessnoise:没有人提倡使用多种中断控制器。问题是您拥有的那个是全局可见的,而不是有人可以创建另一个。
  • 如果您担心能见度,则可以使用工厂。这近乎偏执,我建议最好将设计工作花在其他地方。简单地使用目录来放置标题和 -I 指令可以实现相同的结果和/或部分链接与符号删除。除非您希望人们手动 extern 上课;我认为您在这种情况下存在人员问题;评论也可以提供帮助。我理解这样做的缺点,但我认为在这种情况下它们是有道理的。
  • @artlessnoise 全局状态,当仅通过外观、装饰器、适配器或当今流行的任何东西间接暴露时,仍然是全局状态。如果您可以将您的状态完全隐藏在程序的其余部分中,而不会留下任何痕迹,那么您将拥有更多的权力。在大多数情况下,会有一些需要拉动的因素,会产生明显的后果。没有人会做令人讨厌的非法事情,只需调用完全合法的、有文件记录的、全球可用的函数。