为什么使用fprintf的内联函数需要声明为静态?

13

我正在重构一些C代码并对已提取的部分执行单元测试(使用Google Test)。一个片段在循环中被多次使用,因此为了将其暴露给测试,我将其作为内联函数提取到头文件demo.h中,该头文件还包括一些其他非inline函数的声明。简化版本如下:

#ifndef DEMO_H_
#define DEMO_H_
#ifdef __cplusplus
extern "C" {
#endif
inline void print_line(FILE* dest, const double * data, int length) {
    for (int s = 0; s < length; s++)
        fprintf(dest, "%lf ", data[s]);
    fprintf(dest, "\n");
}
#ifdef __cplusplus
}
#endif
#endif /* MK_H_ */

我的测试代码

#include "gtest/gtest.h"
#include "demo.h"
#include <memory>
#include <array>
#include <fstream>

TEST (demo, print_line) {
    std::array<double,4> test_data = {0.1, 1.4, -0.05, 3.612};

    const char* testfile = "print_line_test.txt";
    {
        auto output_file = std::unique_ptr<FILE, decltype(fclose)*>{
            fopen(testfile, "w"), fclose };
        print_line(output_file.get(), test_data.data(), test_data.size());
    }

    std::ifstream input(testfile);
    double dval;
    for(const auto& v: subsequence_data) {
        input >> dval;
        EXPECT_EQ (v, dval);
    }
    EXPECT_FALSE (input >> dval) << "No further data";
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

在MinGW g++ 4.8.1下,使用-std=gnu++0x编译该代码可以很好地运行。

然后,原始的C代码就会使用这个函数。简化版本如下所示:

#include "demo.h"

void process_data(const char* fname, double ** lines, int num_lines, int line_length) {
    FILE* output_file = fopen(fname, "w");
    for (int i=0; i<num_lines; ++i) {
      print_line(output_file, lines[i], line_length);
    }
}

然而,当我使用MinGW GCC 4.8.1编译我的C代码时,使用-std=c99,我会收到以下警告:

警告:'fprintf'是静态的但在非静态函数'print_line'中使用[默认情况下启用]。

我还会得到一个后续错误,可能与此有关:

对`print_line'的未定义引用

在头文件中更改签名为static inline void print_line ...似乎可以解决问题。然而,我不喜欢不理解这个问题的原因。为什么缺少static不影响C ++测试?关于fprintf的错误实际上意味着什么?


4
第一个问题,“-std=c99”是错误的,因为这不是[C语言]。 - Iharob Al Asimi
1
@iharob,我认为你的说法是错误的。测试代码是C++,但头文件是C语言编写的,并且用于编写C语言代码。我认为我在我的问题中已经非常清楚地表达了这一点。 - beldaz
1
内联函数不会被使用,除非它们在某个地方被调用,然后调用将被替换为函数体(就像宏一样)。因此,在您的代码中,“print_line”指向无处。 - milevyo
你不能从模块外部引用内联函数。 - milevyo
@iharob 我添加了一些代码来让问题更加清晰明了。请告诉我您是否认为问题还需要进一步修改。 - beldaz
显示剩余4条评论
2个回答

9
没有使用 static 关键字,你允许 C99 编译器创建一个具有外部链接的函数(在单个地方定义),同时在包含该文件的每个翻译单元中创建单独的内联代码。编译器可以使用任何它喜欢的函数,除非你明确决定使用 staticextern
这些函数的一个要求可以在 C99 Draft 6.7.4.3 中看到:

带有外部链接的内联函数的内联定义不得包含具有静态存储期限的可修改对象的定义,并且不能包含对具有内部链接的标识符的引用。

这是有道理的,因为编译器希望这个函数的行为相同,无论它选择如何实现它。
因此,在这种情况下,编译器抱怨你的非静态内联函数调用了另一个函数(fprintf),而它不确定这个其他函数是否会改变静态存储内容。

2
谢谢,这特别回答了我所观察到的问题,并帮助解释了那令人困扰的警告。 - beldaz

4
首先,关于涉及符号链接的inline行为,在C和C++中有所不同(并且在ISO C和GNU C之间也存在差异)。
您可以在此处阅读有关C版本的信息here
如果您尝试将函数体放在从C和C++(在同一项目中)都包含的标头中,则会打开一个真正的问题。这种情况不受任何语言标准的覆盖。实际上,我会将其视为ODR violation,因为C版本的函数与C++版本不同。
安全的做法是仅在标头中包含函数原型,并将函数体放在非标头源文件中。

1
谢谢,链接的SO答案本身就是一个很好的资源。将函数放入单独的编译单元似乎是最佳方法,我会在LTO中保留内联。 - beldaz

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