0

While I tried to understand inline functions, I found myself in a rabbit hole which is very deep. I get to know that inline is a function specifier, that inline is a mere hint to a compiler, that it's up to a compiler whether it perform an inline expansion or not, that a complier needs one definition for inline and another definition for a normal function call, etc.

By the way, it seems I'm not the only one who is having a hard time trying to understand this topic, considering the number of questions on it here. Even though I've tried to read those answers, I still feel cryptic.

Nevertheless, what I'm sure is that there are two correct ways to use inline functions, which are static inline and extern inline:

/* Usage of static inline */ /* This is a file named test.c */ #include <stdio.h> static inline int fn(void); int main(void) { printf("%d\n", fn()); return 0; } // Does this provide two definitions? I'm not sure. static inline int fn(void) { return 123; } /* Output */ $ gcc -o test test.c $ ./test 123 
/* Usage of extern inline */ /* In a file named inline.h */ inline int fn(void) { return 321; } /* In a file named inline.c */ #include "inline.h" // So this "declaration" is treated as "definition"? extern int fn(void); /* In a file named test.c */ #include <stdio.h> #include "inline.h" int main(void) { printf("%d\n", fn()); return 0; } /* Output */ $ gcc -o test test.c inline.c $ ./test 321 

Meanwhile, I gave it a try the below code and I got a bizarre result, which is the topic of this question.

/* In a file named test_1.c */ #include <stdio.h> inline int fn(int a, int b) { return a + b; } extern int fn(int a, int b); int wrapper(void); int main(void) { printf("%d\n", wrapper()); return 0; } /* In a file named test_2.c */ inline int fn(int a) { return a; } int wrapper(void) { return fn(123); } 

I wrote two versions of the fn function and I purposely made them different. (fn in test_2.c has only one parameter while fn in test_1.c has two parameters.)

After I compiled this program and executed it, it printed a random number every time I ran it.

$ gcc -o test test_1.c test_2.c $ ./test -1466335670 $ ./test 81565994 

As far as I guess, the fn(123) call in test_2.c used the definition of fn in test_1.c since the compiler needed a normal definition of fn because it didn't choose to inline it, and fn has an external linkage. (If a function has an external linkage, that function is visible from other files.) Meanwhile, it seems the compiler recognized fn has only one parameter because of the fn definition in test_2.c. However, fn(123) lacked of the second parameter. So I think something went wrong because of this reason. Or perhaps it's just because I wrote a code causing undefined behaviors or something.

I'm sorry to add one more 'I-don't-understand-inline-functions' question, but please give me one more generosity. It would be greatly appreciated if you could explain why this happens. Thank you.

EDIT: This is the environment on which I built the executable. Note: I'm using Windows Subsystem for Linux (WSL).

> wsl -l -v NAME STATE VERSION * Ubuntu Running 2 $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.3 LTS Release: 22.04 Codename: jammy $ gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 

This is the commands I used to build the executable.

$ gcc -o test test_1.c test_2.c 

If possible, I would prefer C99, since the book from which I learned C explains C89 & C99. Thank you very much.

6
  • 1
    As always, the outcome of one specific compile has no significance. If you want to investigate that special behavior, look at the generated machine code. We cannot do this for you, because your question lacks build information (compiler, its version, the build command(s)). -- If you want a definite answer based on the statements of the standard (what version, please specify), please edit your question to clarify. Commented Feb 8 at 16:18
  • 1
    If you really want to work with C99 then you need to compile with -std=c99. The default for your version of GCC is gnu17 which is ISO C17 plus GCC-specific extensions. Alternatively, you could get a newer book (or simply be assured that the majority of what you're learning about C99 is still applicable to C11/C17/C23). Commented Feb 8 at 17:18
  • 1
    I am kind of curious what you expected. Even without knowing the exact language rules, you seem to have realized that the code is problematic: the compiler can decline to inline the call in test_2, in which case you are calling the code in test_1, and with the wrong number of arguments. Clearly, no good can come of that. Were you perhaps expecting a compiler error? Most C implementations can't do any type checking across translation units, and so have no way to detect such an error at compile or link time. Commented Feb 8 at 19:30
  • @NateEldredge// I guess I was sleepy last night when I wrote this question. So I'm not sure why I wondered this, but it seems I was curious about whether it be possible for each .c file to have a "different inline" function with the same name when one .c file has an extern declaration of a function with that name. I think this question is just a kind of random curiosity which occurs suddenly when trying to get the hang of something. Yes, maybe I expected a compile error. Commented Feb 9 at 3:04
  • Maybe this question is derived from the two observations: (1) as far as I know, each .c file can have its own (possibly different) definition of a static inline function with the same name. (Since that's the meaning of static.) (2) In the normal usage of extern inline, each .c file includes .h file where the inline definition of a function is. So I thought it's fine for each .c file to have the same "inline" function definition, unlike normal function definitions. I think it causes a compile error to have multiple definitions of a function. Commented Feb 9 at 3:04

1 Answer 1

2

I think your code has undefined behavior. C23 draft N3220 6.2.7p2:

All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined.

You have two declarations of fn with incompatible types (different numbers of arguments).

A possible cause of the actual behavior you observed is that, as you suggested, the compiler chose not to inline the call to fn() in test_2.c (this is normal behavior for gcc when not optimizing: https://godbolt.org/z/dTTGbv9x9), and so emitted a call to the external definition from test_1.c. Since the test_1.c version expected two arguments, but only one was passed, the other probably came from whatever happened to be in the relevant register or memory location, whose value was unpredictable and may have changed from run to run. (If it happened to be an address, and your system uses ASLR, then it might be "random" in a more literal sense.) Obviously, to be certain of this, you'd have to inspect and/or trace the machine code actually generated by your compiler on your system.

If the declarations had been compatible (say, both taking one int argument), and the calls updated to match, then there should be no UB, but it's still "unspecified" (6.7.5p7) whether the inline or the external definition is the one that's called in each instance. As such, as a matter of reasonable programming practice, you should normally ensure that all definitions are identical (say, by having the definition in a single header file which is #included into every translation unit where it's needed); or at least, that they have functionally identical behavior.

If you want to have multiple, substantially different definitions (still with compatible declarations), and "roll the dice" on every call, the standard allows that, but it seems much more likely to be a source of bugs than a useful feature.

(You mentioned being interested in C99: everything in this post is the same for C99 as for C23. The relevant rules have not changed between the two versions.)

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.