为什么要使用静态“常量”而不是实际值?

8

我在研究最小二乘法的代码,发现以下几行:

static double one = 1.0;
static double p1 = 0.1;
static double p5 = 0.5;
...

我在想为什么有人会为1.0这样的微不足道的数值定义一个静态变量,我可以理解为pi这样的值定义一个静态变量,但是对于像1.0和0.1这样的微不足道的数值,这样做可能会使代码难以阅读,但是它可能有其他我不知道的好处。
所以,这些定义有什么原因吗?或者,如果它没有被用于现代代码,那么旧编译器是否有任何原因需要这样做?我知道我正在查看的代码是从FORTRAN翻译到C/C++的。在FORTRAN中有这样做的原因吗?

5
更糟糕的是,它们甚至不是“const”……如果某人需要在某些API调用中使用指针/引用来指向这个数字,那么这可能是有用的,但直接在公式中使用它确实很奇怪。 - Rames
3
这段代码是否包含 foo(&one) 这样的操作?我认为这可能与Fortran的参数传递规则有关。 - user2357112
1
@user2357112除了简单的数学运算,-one、one + ...等等,我看不出其他用途。 - triple_r
2
软件工程.SE上有一些相关历史,关于旧的Fortran编译器允许的内容:曾经改变过4的值吗? - hardmath
1
我并没有阅读所有的评论和答案,但据我所知,在旧的Fortran77代码中设置常量像“zero = 0.0d0,one = 1.0d0,two = 2.0d0”是非常常见的做法(有时我也会这样做)。在我的情况下,这是为了更好的可读性,而不是为了优化。我认为你可以在许多旧的库代码中找到类似的例子(比如在netlib等中)。 - roygvib
显示剩余15条评论
5个回答

6
我知道我看到的代码是从FORTRAN翻译成C/C++的。在FORTRAN中是否有任何原因?
FORTRAN对于所有子程序参数使用传递引用(pass-by-reference)。然而,与其他使用传递引用的语言不同,它仍然允许您将“rvalues”作为参数传递。在幕后,FORTRAN编译器会转换以下代码:
CALL SUBFOO(X + Y, 4)

像编码一样

TEMP1 = X + Y
TEMP2 = 4
CALL SUBFOO(TEMP1, TEMP2)

这正是你的C++代码正在做的事情:创建变量以便像FORTRAN那样传递指针(或引用)给子程序。
当然,这不是C或C ++的惯用方式。通常,你会通过值来传递一个双精度浮点数。
(代码示例摘自我在这个问题上的答案。)

谢谢,那么通过定义 four = 4 并使用 call sub(four) ... call sub2(four) ... 而不是使用字面量 4.0,是否有性能提升?现在还是这样吗?或者新的编译器会处理这个问题吗? - triple_r
2
这是我的假设,但显然这些变量的大多数用途实际上并不是作为函数参数。看起来他们只是机械地替换了每个字面量,可能是因为这比特定地针对函数参数更容易。另外,Fortran参数关联并不完全是按引用传递; 有一些奇怪的特殊情况。 - user2357112
1
@user2357112 这是真的,除了一行代码:temp = mp_dmax1(zero, one - temp*temp); 其余的代码据我所见都没有被用作任何函数调用的参数。 - triple_r
@triple_r: one - temp*temp - 好吧,这又对这个假设造成了另一个很大的打击,因为如果这个做法的目的是为了保留原始的Fortran参数传递语义,那么one - temp*temp临时变量必须被保存到一个变量中。听起来这些zeroone等变量完全没有意义。 - user2357112
@user2357112 我现在想明白为什么它们是这样做的了。正如有人在问题的评论中提到的那样,这是为了方便将代码转换为单精度和双精度运行。因此,将代码中的所有数字分配给变量,很容易改变精度(例如,将double one = 1.0更改为float one = 1.0,而不是将所有的1,0更改为1.0f)。 - triple_r

5

使用

static double one = 1.0;
static double p1 = 0.1;
static double p5 = 0.5;

这对我来说没有太多意义。如果变量名字更有意义,那么这样做就会有意义。我认为像这样是有价值的:

static double defaultCoefficient = 1.0;
static double defaultRateOfIncrease = 0.1;
static double defaultRatio = 0.5;

在第二组中,代码中使用变量比使用相应的常量更有意义。将它们设置为常量会更有意义。
static double const defaultCoefficient = 1.0;
static double const defaultRateOfIncrease = 0.1;
static double const defaultRatio = 0.5;

我知道我正在查看的代码是从FORTRAN翻译成C/C++的。

如果翻译是由程序完成的,那么变量取名的方式就可以理解了。

如果翻译是由人完成的,他们可能遵循了某些快速移动代码的指导方针,而不一定通过理解代码来创建有意义的变量名称。


这很有道理,但在这段代码中,它是字面值 1.0,因此例如,当他们想要使用单位矩阵时,通过初始化实现:diag[i] = one; - triple_r
5
这些仍应该被制成常量。 - Lightness Races in Orbit
5
“one” 是100%愚蠢的,毫无疑问。但是公平地说,如果像 p1p5 这样的名称是指实现科学论文中的变量 p1p5 ,那么它们就有意义了。当然,如果它们代表着“[零]点一”和“[零]点五”,那么它们和 one 一样愚蠢。 - Angew is no longer proud of SO
2
const 更有用的是 constexpr - Guillaume Racicot
2
真是个笑话,@marshal - Lightness Races in Orbit
显示剩余9条评论

5

我曾经见过这种情况在一些使用哈佛架构的(旧的、嵌入式)平台上使用。

假设我有一个(外部库)函数,它接受一个指针作为参数:

void Foo(double *Bar);

如果我想将一个常量传递给这个函数,显然我不能直接写。我必须像在开头的帖子中那样做,这样我就可以取出one的地址。

Foo(&one);

在一些哈佛结构的平台上,const值被放置在程序ROM或闪存中,而不是RAM中,因此这样做将无法正常工作,因为你会获取ROM地址而不是所需的RAM地址。
将其变成全局(静态)变量可以使其在启动代码中初始化一次(从ROM复制到RAM一次),而不是每次需要时都将其变成自动变量。
我承认这是一个利基用例。

C标准允许这样的内存模型吗?我知道它允许代码指针在不同的地址空间中与数据指针(对于某些x86-16内存模型是必要的),但即使该对象永远不会通过该指针被修改,它是否可以禁止将非const的T*指向全局的const T对象? - dan04
@dan04:发现了这个链接:https://www.securecoding.cert.org/confluence/display/c/EXP05-C.+Do+not+cast+away+a+const+qualification 。看起来强制转换掉“const”是未定义行为。如果没有进行强制转换,应该无法编译(在gcc上使用“-pendantic-errors”进行验证)。 - Unimportant

3
不,这个常量很傻。即使没有其他原因,也是因为它实际上并不是一个常量。

你能在这个主题上再多解释一点吗? - user2736738
2
你需要更多的解释吗?你看,这就是为什么基于观点的帖子在这里是不被允许的。我其实不应该回答! - Lightness Races in Orbit
不,你回答得很好,这很有帮助,但我只是在问……静态变量不是常量,这就是你说的对吧?但实际上并不是这样的。 - user2736738
@LightnessRacesinOrbit 我想你可以通过一个示例来支持你的非常量断言,展示这个断言是如何被打破的。这样更多的读者就能理解你的观点了。 - Peter Camilleri
@PeterCamilleri:我不明白你所说的“一个展示如何打破这个的例子”。问题是关于常量的,但是所展示的声明并不是常量;就是这样!这就是我的意思。 - Lightness Races in Orbit
显示剩余2条评论

1
实际上,这样做有一个很好的理由,它涉及到软件开发过程本身。
假设你有一个翻译单元,调用一些带有数字参数的函数,自然地,你会将其设置为常量。
static const double epsilon = 1.e-3;

很好,干净的代码。但现在你意识到你设置的 epsilon 不是很好,你需要一个更好的。你没有确定它应该是什么的确切方法,所以你进行一些试错:
static const double epsilon = 1.e-4;

你重建了程序,但它仍然不够好。如果你再次更改它,你需要等待构建完成,对于一些非常规项目来说这可能需要一段时间。该怎么办呢?
好的,调试器可以让你改变变量的值,只要它们存在于内存中(而不是像真正的常量一样被消除)。所以我们做如下操作:
static double epsilon = 1.e-4;

现在我们在该文件中的某个位置设置了一个断点。我们可以修改 epsilon 而无需每次重新构建程序。这样可以节省宝贵的开发时间。并且我们很快就能找到一个合适的值。
那么,我们将它留作非 const 吗?不是的。这是一个常量,因此在检查代码之前,我们将其标记为 const。将其保持为非常量是一种代码异味。这没有进一步的目的。

虽然我喜欢关于变量与常量在调试目的上的观点,但我个人不喜欢将C语法转化为面向对象的原则的思想。例如,我一直不喜欢他们通常教授面向对象编程的方式,我应该去想,“哦,一个狗自然就是一个对象...”然后继续进行面向对象编程。对我来说,常量除了在编程语言中的功能和对程序执行的影响之外,没有其他含义。对我来说,我会将常量用于在软件生命周期内不会改变的数据。 - marshal craft
@marshalcraft - "将C语法转化为面向对象的原则的意识形态",我不太明白。常量应该被指定为常量。C++使用const,而C使用宏来实现。除了强制执行特定的设计之外,没有其他意识形态。 - StoryTeller - Unslander Monica
我不会在语法声明修饰符和常量这两个词之间建立任何重要的联系。一个具有数学/物理意义,而另一个则涉及算法,并且更加明确。 - marshal craft
我再次看到常量被用作程序版本号或任何不会在程序中改变的东西。在许多情况下,函数很容易受益于以编程方式定义的“常量”。因此,虽然您的临时设计实践在某些可以想象的用例中肯定具有价值,但我认为更常见的情况是它是用于业务项目经理和分析师管理人员的工具,而不是用于软件开发,因为许多设计实践与可量化的标准无关,而是与管理职位上那些解释相关。即“代码可读性”。 - marshal craft
@marshalcraft - 你参与过许多大型软件项目吗?我想知道对于所有与软件设计相关的事情,那种蔑视的态度是从哪里来的。 - StoryTeller - Unslander Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接