I am refactoring some C code and performing unit testing on the factored out parts (using Google Test). One snippet was used several times in a loop, so in order to expose it to testing I factored it out as an inline
function in a header file demo.h
which also includes declarations of some other non-inline
functions. A simplified version is as follows:
#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_ */
My test code
#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();
}
This code compiles and runs fine under MinGW g++ 4.8.1 with -std=gnu++0x
.
The original C code then makes of use of this function. A simplified version would be the following:
#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);
}
}
However, when I try to compile my C code using MinGW GCC 4.8.1 with -std=c99
, I get the following warning:
warning: 'fprintf' is static but used in inline function 'print_line' which is not static [enabled by default]
I also get a subsequent error, which may be related:
undefined reference to `print_line'
Changing the signature in the header to static inline void print_line ...
appears to fix the problem. However, I don't like not understanding the cause of the issue. Why did the lack of static
not affect the C++ test? And what did the error regarding fprintf
actually mean?
In C99, an inline or extern inline function must not access static global variables or define non- const static local variables. const static local variables may or may not be different objects in different translation units, depending on whether the function was inlined or whether a call was made.
Static local variables are not allowed to be defined within the body of an inline function. C++ functions implemented inside of a class declaration are automatically defined inline.
An inline function is one for which the compiler copies the code from the function definition directly into the code of the calling function rather than creating a separate set of instructions in memory. This eliminates call-linkage overhead and can expose significant optimization opportunities.
A static inline function is, in practice, likely (but not certain) to be inlined by some good optimizing compiler (e.g. by GCC when it is given -O2 ) at most of its call sites. It is defined in a header file, because it then could be inlined at most call sites (perhaps all of them).
Without static
, you are allowing a C99 compiler to create both a function with external linkage (defined in a single place), but also separate inline code in every translation unit that includes the file. It can use any function it likes, unless you explicitly decide between static
or extern
.
One requirement of such functions can be seen in C99 Draft 6.7.4.3:
An inline definition of a function with external linkage shall not contain a definition of a modifiable object with static storage duration, and shall not contain a reference to an identifier with internal linkage.
This makes sense, because compiler wants this function to behave equally, regardless of how it chooses to implement it.
So, in this case the compiler is complaining that your non-static inline function is calling a different function which is static
, and it isn't sure that this other function (fprintf
) doesn't mutate static storage.
First of all, the behaviour of inline
, particularly with respect to linkage of the symbols involved, is different in C to C++. (And also differs between ISO C and GNU C).
You can read about the C version here.
If you try putting a function body in a header that is included from both C and C++ (within the same project) then you are opening a real can of worms. That situation isn't covered by either language standard. In practical terms I would treat it as an ODR violation because the C version of the function is different to the C++ version.
The safe thing to do is to include only the function prototype in the header, and have the function body in one of the non-header source files.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With