何时在C中将局部变量声明为静态



我最近了解了C中的存储类。特别是static存储类让我着迷。在Haskell中,我避开了将输出缓冲区传递给函数以获得结果的概念。例如,考虑以下readfile函数:

#include <stdio.h>
void readfile(const char * filename, char * contents, size_t size) {
FILE * file = fopen(filename, "rb");
fread(contents, size, 1, file);
contents[size] = 0;
fclose(file);
}

我不喜欢这样的代码有几个原因:

  1. 使用void作为返回类型让我很恼火。我不知道为什么。确实如此
  2. 将输出缓冲区传递给函数似乎不自然。可变状态容易出错
  3. 您不应该将要读取的字节数作为函数的输入参数
  4. 您需要手动创建一个缓冲区并预测文件的大小

由于这些问题,我重写了上面的代码如下:

#include <stdio.h>
#include <malloc.h>
char * readfile(const char * filename) {
FILE * file = fopen(filename, "rb");
fseek(file, 0, SEEK_END);
size_t size = ftell(file);
fseek(file, 0, SEEK_SET);
static char * contents;
contents = malloc(size + 1);
fread(contents, size, 1, file);
fclose(file);
contents[size] = 0;
return contents;
}

现在,读取文件内容所需要做的就是将文件名传递给readfile。它为文件的内容分配空间,并返回一个指向新创建的缓冲区的指针。你唯一需要做的记账就是在用完缓冲区后free

正如您在上面的代码中所看到的,我已经将contents声明为static,因此该变量只有一个实例,并且您可以返回它,而编译器不会向您发出警告。在我看来,这是一个比使用全局变量更干净的解决方案。

尽管如此,我对在生产代码中使用static持怀疑态度:部分原因是我害怕与共享变量耦合的可变状态,部分原因是这是我第一次使用它。如图所示,使用static的潜在风险是什么?

例如,当您同时读取两个文件时,上面的代码会给出错误的结果吗?如何在不返回C风格代码的情况下解决这些问题(例如,将输出缓冲区传递给函数等)

您几乎从不想在C.中将局部变量声明为static

使变量static成为一个全局变量,其名称在该函数之外是不可访问的。正如你可能很清楚的那样,全局变量可能是有问题的:如果你从两个不同的线程运行readfile,你可能会有第一个调用malloc并将结果存储到contents中,第二个线程调用malloc并将结果存储到contents中,然后第一个线程fread到分配的第二个contents中,如果在第二线程上读取的文件较小并且在任何情况下都是不希望的,则这可能导致缓冲区溢出。

使用static的原因可能是contents以前是一个数组。如果是这样的话,编译器会正确地警告您不能将其返回给调用方:它将分解为指针,但一旦函数返回,本地数组变量就会被破坏,指针也会变为无效。将其声明为static使其返回有效,因为它是一个全局变量,所以当函数退出时,它不会被销毁,指针将保持有效。不过,如果将它与线程一起使用,仍然会出现问题。

您可能想要使用static的唯一时间是,如果您有一些仅在函数中使用的常量数据。例如:

static const int some_integers[] = { 1, 2, 3, 4 };

这样可以节省一些堆栈空间。

为了避免我忘记提及您的特定代码,删除static可以使其按需工作。如果在现实生活中使用这些代码,我会确保添加一些错误检查,因为您调用的几乎所有函数都可能失败,并且只能通过返回值发出信号。

在这种情况下使用static将有效地使变量的内存存储在进程内存的全局数据段中。但是,它将只具有本地作用域(只有此函数才能看到它)。由于您可能会以其他方式(返回值)跟踪指针,因此没有必要使用此方法,并且会浪费相当于指针的全局数据内存(可能不是什么大不了的事)。

还有像stat(2)fstat(2)这样的POSIX函数,可以让您找到文件的确切大小。您可以使用它一次性分配缓冲区和读取数据。

如果您的文件相当大,您也可以使用mmap(2)系统调用或CreateFileMappingMapViewOfFile让操作系统为您完成大部分繁重的工作。

最新更新