Asked  6 Months ago    Answers:  5   Viewed   20 times

When I try to build this code

inline void f() {}

int main()
{
    f();
}

using the command line

gcc -std=c99 -o a a.c

I get a linker error (undefined reference to f). The error vanishes if I use static inline or extern inline instead of just inline, or if I compile with -O (so the function is actually inlined).

This behaviour seems to be defined in paragraph 6.7.4 (6) of the C99 standard:

If all of the file scope declarations for a function in a translation unit include the inline function specifier without extern, then the definition in that translation unit is an inline definition. An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit. An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit. It is unspecified whether a call to the function uses the inline definition or the external definition.

If I understand all this correctly, a compilation unit with a function defined inline as in the above example only compiles consistently if there is also an external function with the same name, and I never know if my own function or the external function is called.

Isn't this behaviour completely daft? Is it ever useful to define a function inline without static or extern in C99? Am I missing something?

Summary of answers

Of course I was missing something, and the behaviour isn't daft. :)

As Nemo explains, the idea is to put the definition of the function

inline void f() {}

in the header file and only a declaration

extern inline void f();

in the corresponding .c file. Only the extern declaration triggers the generation of externally visible binary code. And there is indeed no use of inline in a .c file -- it's only useful in headers.

As the rationale of the C99 committee quoted in Jonathan's answer explicates, inline is all about compiler optimisations that require the definition of a function to be visible at the site of a call. This can only be achieved by putting the definition in the header, and of course a definition in a header must not emit code every time it is seen by the compiler. But since the compiler is not forced to actually inline a function, an external definition must exist somewhere.

 Answers

50

Actually this excellent answer also answers your question, I think:

What does extern inline do?

The idea is that "inline" can be used in a header file, and then "extern inline" in a .c file. "extern inline" is just how you instruct the compiler which object file should contain the (externally visible) generated code.

[update, to elaborate]

I do not think there is any use for "inline" (without "static" or "extern") in a .c file. But in a header file it makes sense, and it requires a corresponding "extern inline" declaration in some .c file to actually generate the stand-alone code.

Tuesday, June 1, 2021
 
footy
answered 6 Months ago
14

Note: when I talk about .c files and .h files in this answer, I assume you have laid out your code correctly, i.e. .c files only include .h files. The distinction is that a .h file may be included in multiple translation units.

static inline void f(void) {} has no practical difference with static void f(void) {}.

In ISO C, this is correct. They are identical in behaviour (assuming you don't re-declare them differently in the same TU of course!) the only practical effect may be to cause the compiler to optimize differently.

inline void f(void) {} in C doesn't work as the C++ way. How does it work in C? What actually does extern inline void f(void); do?

This is explained by this answer and also this thread.

In ISO C and C++, you can freely use inline void f(void) {} in header files -- although for different reasons!

In ISO C, it does not provide an external definition at all. In ISO C++ it does provide an external definition; however C++ has an additional rule (which C doesn't), that if there are multiple external definitions of an inline function, then the compiler sorts it out and picks one of them.

extern inline void f(void); in a .c file in ISO C is meant to be paired with the use of inline void f(void) {} in header files. It causes the external definition of the function to be emitted in that translation unit. If you don't do this then there is no external definition, and so you may get a link error (it is unspecified whether any particular call of f links to the external definition or not).

In other words, in ISO C you can manually select where the external definition goes; or suppress external definition entirely by using static inline everywhere; but in ISO C++ the compiler chooses if and where an external definition would go.

In GNU C, things are different (more on this below).

To complicate things further, GNU C++ allows you to write static inline an extern inline in C++ code... I wouldn't like to guess on what that does exactly

I never really found a use of the inline keyword in my C programs, and when I see this keyword in other people's code, it's almost always static inline

Many coders don't know what they're doing and just put together something that appears to work. Another factor here is that the code you're looking at might have been written for GNU C, not ISO C.

In GNU C, plain inline behaves differently to ISO C. It actually emits an externally visible definition, so having a .h file with a plain inline function included from two translation units causes undefined behaviour.

So if the coder wants to supply the inline optimization hint in GNU C, then static inline is required. Since static inline works in both ISO C and GNU C, it's natural that people ended up settling for that and seeing that it appeared to work without giving errors.

, in which I see no difference with just static.

The difference is just in the intent to provide a speed-over-size optimization hint to the compiler. With modern compilers this is superfluous.

Wednesday, June 9, 2021
 
muaddhib
answered 6 Months ago
57

Global variables are not extern nor static by default on C and C++. When you declare a variable as static, you are restricting it to the current source file. If you declare it as extern, you are saying that the variable exists, but are defined somewhere else, and if you don't have it defined elsewhere (without the extern keyword) you will get a link error (symbol not found).

Your code will break when you have more source files including that header, on link time you will have multiple references to varGlobal. If you declare it as static, then it will work with multiple sources (I mean, it will compile and link), but each source will have its own varGlobal.

What you can do in C++, that you can't in C, is to declare the variable as const on the header, like this:

const int varGlobal = 7;

And include in multiple sources, without breaking things at link time. The idea is to replace the old C style #define for constants.

If you need a global variable visible on multiple sources and not const, declare it as extern on the header, and then define it, this time without the extern keyword, on a source file:

Header included by multiple files:

extern int varGlobal;

In one of your source files:

int varGlobal = 7;
Wednesday, June 9, 2021
 
barden
answered 6 Months ago
97

Yes, you are just lucky :) The extern "C" is one language linkage for the C language that every C++ compiler has to support, beside extern "C++" which is the default. Compilers may supports other language linkages. GCC for example supports extern "Java" which allows interfacing with java code (though that's quite cumbersome).

extern "C" tells the compiler that your function is callable by C code. That can, but not must, include the appropriate calling convention and the appropriate C language name mangling (sometimes called "decoration") among other things depending on the implementation. If you have a static member function, the calling convention for it is the one of your C++ compiler. Often they are the same as for the C compiler of that platform - so i said you are just lucky. If you have a C API and you pass a function pointer, better always put one to a function declared with extern "C" like

extern "C" void foo() { ... }

Even though the function pointer type does not contain the linkage specification but rather looks like

void(*)(void)

The linkage is an integral part of the type - you just can't express it directly without a typedef:

extern "C" typedef void(*extern_c_funptr_t)();

The Comeau C++ compiler, in strict mode, will emit an error for example if you try to assign the address of the extern "C" function of above to a (void(*)()), beause this is a pointer to a function with C++ linkage.

Sunday, June 13, 2021
 
tika
answered 6 Months ago
93

When code compiles with C99, it conform to the C99 standard, which does not have stricmp(). When code compile without C99 switch, it conforms to an unknown standard that implements stricmp(). (Given gcc without -std=c99, likely compiles to the C89/90 standard wihich allows implicit declarations.)

As @Joachim Pileborg commented, insensitive compares are not part of the C standard.

With C99 implicit functions require a diagnostic (a warning in this case). Without C99, the implicit use of the function generates no warning. The functions exists in this compiler's library - it is just a question of are the functions declared before use.

Easy enough to make your own:

int wal_stricmp(const char *a, const char *b) {
  int ca, cb;
  do {
     ca = (unsigned char) *a++;
     cb = (unsigned char) *b++;
     ca = tolower(toupper(ca));
     cb = tolower(toupper(cb));
   } while (ca == cb && ca != '');
   return ca - cb;
}

Note: When coding and trying to make A-Z match a-z, string insensitive compare routines tend to work uniformly well. But when trying to to order strings, things quickly get out of hand. "abc" vs. "_bc" can come before or after the other depending on if compassion was done as upper or lower case. '_', in ASCII, exists between the upper and lower case letters. With internationalization and locale issues, the situation becomes more complex. My code example uses a round-trip of conversion to cope with issues where the number of uppercase char does not have a 1-to-1 mapping with lowercase ones. IMO the complexities of robust case insensitive compares obliges the use of UTF encoding and its case definition.


[Edit 2020]

To cope with those forlorned non-2's complement as well as 2's complement platforms, a code correction is warranted. Earlier code would fold a +0 and -0 into an unsigned 0. Only the +0 should convert to 0. Proper to read the data as unsigned char rather than signed char and convert.

Note: the proper handle in non-2's complement is mostly academic now.

// ca = (unsigned char) *a++;
ca = *((unsigned char *) a++);
// also cb
Monday, August 2, 2021
 
Trott
answered 4 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share