C语言中的静态初始化

3
我有一个函数,它接受一个整数列表,直到其中一个值为“-1”,并计算最小值。
如果该函数被多次调用,则应返回所有调用之间的最小值。
因此,我编写了以下代码:
int min_call(int num, ...)
{
    va_list argptr;
    int number;

    va_start(argptr, num);

    //static int min = va_arg(argptr, int); //////// the questioned line

    static int all_min = -1;

    int min = va_arg(argptr, int);

    if (min != -1)
    {
        while ((number = va_arg(argptr, int)) != -1)
        {
            if (number < min)
            {
                min = number;
            }

        }
    }

    if (min < all_min || all_min == -1)
    {
        all_min = min;
    }

    return all_min;
}

我想了解关于标记行的一些内容...为什么我不能称之为 - 编译器说因为用于初始化静态int的表达式不是常量。

由于某种原因,我记得我可以初始化一个静态变量,并且知道初始化语句只会在第一次编写时调用(第一次)它在C ++中可用的话,它将节省我几个变量。

在这个问题上,C和C++有区别吗?


还有一件事。我建议不要将all_min初始化为-1,而是包含<limits.h>并将其初始化为INT_MIN。 - R Samuel Klatchko
1
你指的是 INT_MAX,请看我的答案。 - Alok Singhal
6个回答

9

是的,C++允许在运行时惰性初始化静态变量。实际上,C++将静态初始化转化为以下形式:

static int XX_first_time = 1;
if (XX_first_time)
{
    // run the initializer
    XX_first_time = 0;
}

虽然这种方式很方便,但它并不是线程安全的。虽然一些编译器已经实现了线程安全的初始化(例如gcc 4.x除非使用-fno-threadsafe-statics显式禁用),但标准并没有要求这样做。
C语言要求静态变量在编译时就确定其值。是的,这样做更加有限,但更符合C语言不在背后为你做过多工作的特点(可以将C语言视为可移植汇编语言)。

这不是应该写成 "static int XX_first_time = 1;" 吗?(开玩笑) - Martin York
@Martin - 哎呀!已经修复了(不是开玩笑)。 - R Samuel Klatchko
1
仅最近,C++编译器才开始以线程安全的方式处理此问题 - 实际上,它们甚至没有义务这样做(因为标准并未就线程安全性发表任何声明,并明确指出在静态初始化器内部仍在运行时再次调用相同的静态函数)。例如,VC++不会进行线程安全的本地静态初始化。 - Pavel Minaev
2
此外,示例扩展略有不妥。标准要求,如果本地静态初始化程序引发异常,则该变量应被视为未初始化 - 即,在下次进入函数时,应再次运行初始化程序。因此,正确的扩展应为 if (XX_first_time) { /*运行初始化程序*/; XX_first_time=0; } - Pavel Minaev
@Pavel - 感谢您对微小错误的解释。我已经更新了我的示例。 - R Samuel Klatchko

4
正如其他人所说,在C语言中,static变量必须使用编译时常量进行初始化。如果省略初始值,则会被视为0
现在,关于您函数中的逻辑。我认为这个函数写起来更清晰,像这样(我没有改变函数的形式):
#include <limits.h>

int min_call(int num, ...)
{
    va_list argptr;
    int number;
    static int min = INT_MAX;

    va_start(argptr, num);

    while ((number = va_arg(argptr, int)) != -1)
        if (number < min)
            min = number;

    va_end(argptr);
    return min;
}

如果要在最小值的计算中包括num,则需要进行附加检查,例如:if (num < min) min = num;
我也不太理解if (min < all_min || all_min == -1 )这行代码。由于all_min在开始时被初始化为-1,然后从未被更改,所以if条件将始终为真。
请注意,min的初始化值为INT_MAX而不是一个评论中提到的INT_MIN,我们必须调用va_end()

请注意,仅在第一次调用函数时使用该功能。但我认为我会使用INT_MAX...谢谢! - Idan
我不知道你所说的“note always”的意思。假设你的意思是“not always”,那指的是什么? - Alok Singhal
啊,我明白了。我认为你根本不需要all_min,但看起来你已经知道发生了什么事情了。 - Alok Singhal

4

C语言中的静态初始化必须使用常量值,即在编译时就已知的值。因为调用va_arg()直到运行时才能解析,所以无法将其用作静态初始化程序。您可以在初始化时将已知的错误值分配给min,然后在变量初始化后简单地调用va_arg()(John Dibling的示例代码正是这样做的)。

此外,需要注意的是,静态初始化仅在每个范围(在本例中为函数)中发生一次,因此即使您注释掉的行起作用,它也只会在函数被调用时执行一次(我认为您是期望每次都执行)。


2
由于静态变量在程序开始运行之前就已经初始化了,所以你无法做你想做的事情 -- argptr 在这个上下文中还不存在。你可以尝试像这样做:
va_start( argptr, num );

static bool  init = 0;
static int min = -1;
if( !init )
{
   min = va_arg( argptr, int );
   init = true;
}

static int all_min = -1;

2

这并不能完全回答你的问题,但其他答案已经很好地回答了。

然而,我认为你从错误的方向来解决这个非常简单的问题。如果你想让你的函数具有可移植性和重用性,你应该尽量减少它们的副作用。

例如,不要在函数中保存状态数据(邪恶),而应该将该数据保存在使用它的范围内的变量(或结构体)中。像这样:

{
    int min_so_far;

    min_so_far = min_call(NULL, 4, 7, 2, -6, 3, -12);
    min_so_far = min_call(&min_so_far, -11, 5, 3, 8, -3);

    printf("%d\n", min_so_far); //should output -12
}
min_call 的第一个参数将是一个 int*,如果它作为 NULL 传递,则函数将忽略它。否则,每个参数将与传入的 int 进行比较。
这种方法可能不太方便,但绝对更安全,而且您将能够在应用程序中多次使用此函数。考虑代码的可重用性是一个好习惯。

你应该将min_so_far声明为int*,否则在调用函数时传递min_so_far的地址。如果不这样做,它可能会编译,但会有严重的逻辑错误(和/或分段错误)。 - Platinum Azure
你说得对,抱歉,我的工作让我一直处在 PHP 的世界里,我忘记了如何正确使用指针。 - Carson Myers
你所说的“安全”,是指线程安全吗? 为什么在函数中保持状态不好? - Idan

2

静态并不仅仅意味着它只在第一次调用时设置。这意味着它是在编译时设置的。编译器不知道如何在编译时调用您的va_arg函数。(在运行时并传递参数运行程序之前,它没有任何意义。)

(编辑-C语言。其他人指出C++有所不同。)


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