Inline function in program - Page 1
Forum section: electronics / general

author: Kittu20
author_rank: Regular Contributor
timestamp: November 06, 2023, 03:03:46 pm
content: Hello everyone,I am trying to understand when can inline function may be useful Take example of my code for reference I wrote previously. I am calling all function in main functionCode:#include <xc.h>#include <stdio.h>#include"config.h"void initializeOscillator(void);void initializeEUSART1(void);/****** NOTE: This function configures the oscillator settings for the microcontroller.* It sets the IRCF bits to 110 for HF-INTOSC/2 (8 MHz, default).*****/void initializeOscillator(void){OSCCON = 0x60; // Set IRCF bits to 110 for HF-INTOSC/2 (8 MHz, default)OSCCON2 = 0x00;OSCTUNE = 0x00;REFOCON = 0x00;}void initializePort() {// Set LATx registers to initial states (all low)LATA = 0x00;LATB = 0x00;LATC = 0x00;LATD = 0x00;LATE = 0x00;// Set TRISx registers to configure pins as outputs (all output)TRISA = 0x00;TRISB = 0x00; // RB0 is RED, RB1 is YELLOW and RB2 is GREEN LEDTRISC = 0x80; // RC7 is TX and RC6 TXTRISD = 0x00;TRISE = 0x00;// Additional configuration for unused peripheralsANCON0 = 0x00; // Set all analog pins to digital modeANCON1 = 0x00; // Set all analog pins to digital modeCM1CON = 0x00; // Turn off Comparator 1CM2CON = 0x00; // Turn off Comparator 2ADCON0 = 0x00; // Disable A/D conversionADCON1 = 0x00; // Disable A/D conversionADCON2 = 0x00; // Disable A/D conversion}/******* NOTE: This function configures the EUSART1 settings for communication.******/void initializeUART1() {// Configure BAUDCON1 register// Bit 7: ABDOVF = 0 (Auto-Baud Acquisition Rollover Status bit - Cleared in software)// Bit 6: RCIDL = 0 (Receive Operation Idle Status bit - Receive operation is active)// Bit 5: RXDTP = 0 (Received Data Polarity Select bit - Receive data is not inverted)// Bit 4: TXCKP = 0 (Clock and Data Polarity Select bit - Idle state for transmit is high level)// Bit 3: BRG16 = 1 (16-Bit Baud Rate Register Enable bit - 16-bit Baud Rate Generator)// Bit 2: Unimplemented: Read as ?0? (Reserved, read as '0')// Bit 1: WUE = 0 (Wake-up Enable bit - RX pin is not monitored or rising edge is detected)// Bit 0: ABDEN = 0 (Auto-Baud Detect Enable bit - Baud rate measurement is disabled or completed)BAUDCON1 = 0b00001000; // Set BRG16 bit to 1 (16-Bit Baud Rate Register Enable)// Calculate and set SPBRG1 for a baud rate of approximately 9600 at 8 MHzSPBRG1 = 207;SPBRGH1 = 0;// TXSTA1 register configuration// Bit 7: CSRC = 0 (Clock Source Select bit - Don't care in Asynchronous mode)// Bit 6: TX9 = 0 (8-bit transmission)// Bit 5: TXEN = 1 (Transmit is enabled)// Bit 4: SYNC = 0 (Asynchronous mode)// Bit 3: SENDB = 0// Bit 2: BRGH = 1 (High-speed baud rate)// Bit 1: TRMT = 1 (TSR is empty initially)TXSTA1 = 0b00100100; // Configure TXSTA1 register// RCSTA1 register configuration// Bit 7: SPEN = 1 (Serial Port Enable bit - Serial port is enabled)// Bit 6: RX9 = 0 (8-bit reception)// Bit 5: SREN = 0 (Single Receive Enable bit - Don't care in Asynchronous mode)// Bit 4: CREN = 1 (Continuous Receive Enable bit - Enables receiver)// Bit 3: ADDEN = 0 (Address Detect Enable bit - Don't care in Asynchronous mode)// Bit 2: FERR = 0 (Framing Error bit - No framing error)// Bit 1: OERR = 0 (Overrun Error bit - No overrun error)RCSTA1 = 0b10010000; // Configure RCSTA1 register}void hardwareInitialize() {// Initialize the oscillatorinitializeOscillator();// Initialize Port PinsinitializePort();// Initialize Uart1initializeUART1();}/******* NOTE: This function waits until the transmitter buffer is empty (TXIF is set)* and then sends the character by placing it in the TXREG1 register.******/void UARTWrite(char Character){// Wait until the transmitter buffer is empty (TXIF is set)while (TXIF == 0);// Send the character by placing it in the TXREG1 registerTXREG1 = Character;}/******* NOTE: This function waits until the receiver buffer is full (RCIF is set)* and then reads the received character from the RCREG1 register.******/char UARTRead(void){// Wait until the receiver buffer is full (RCIF is set)while (RCIF == 0);// Read the received character from the RCREG1 registerreturn RCREG1;}/******* main - Main program function******/void main(void){// Initialize hardwarehardwareInitialize();// Delay for 5 seconds__delay_ms(5000);while (1){// Define a character to sendchar Character = 'U';char ReceivedChar;ReceivedChar = UARTRead(); // Read a command from UARTUARTWrite(ReceivedChar); // Send the command back// Delay for 5 seconds before the next transmission__delay_ms(5000);}}

author: shtirka
author_rank: Contributor
timestamp: November 06, 2023, 05:12:48 pm
content: Hello,Normally function inlining will give you an advantage when you have a short function that you call often - it can increase code efficiency and the speed of code of execution if you do it correctly. The potential problem that I see with inlining any of the functions in the code example that you provided is that you dont call them often enough to see the benefit of improved execution speed and efficiency and also you are working with a UART - you wont see any benefit of inlining UART fuctions either. In fact in this case at least you could (potentially at least) get more code bloat rather than more efficient codeIlya

author: Warhawk
author_rank: Frequent Contributor
timestamp: November 06, 2023, 05:28:40 pm
content: I am not an embedded guru but here is what I think. Inline functions are like macros. It is worth using them in applications when critical parts of the code have to be executed quickly without additional calls or e.g. accesses to external memory.One big benefit of inline functions is that the compiler checks for types. If you pass incorrect arguments to macros, you get whatever. Imagine you have a macro that returns smaller of two variables MIN(a,b). You won't get any error if a is signed and b is unsigned. If you create an inline function for the same, you get warning during compilation (in case input types don't match the function declaration).Another way to think about inline functions is that they allow for tradeoffs between execution time and program memory footprint.

author: ejeffrey
author_rank: Super Contributor
timestamp: November 06, 2023, 06:11:30 pm
content: Inlining is an optimization, so it makes your code execute faster. In order for the compiler to inline you generally need the function to be defined in the same translation unit as the call site (unless you enable link time optimization). The compiler decides based on optimization level and various heuristics.The inline keyword should be used for functions defined in header files to avoid violating the one definition rule. It is neither required to get inlining nor does it mandate that the compiler inline. It's purpose is that by putting the definition in a header you can have it visible for inlining in multiple translation units.

author: ejeffrey
author_rank: Super Contributor
timestamp: November 06, 2023, 06:19:01 pm
content: Quote from: shtirka on November 06, 2023, 05:12:48 pmFor very simple functions such as those UART calls, the code to save and restore registers for a function call may well be more code than the function body, so inlining can actually reduce the code footprint. Most compilers would always inline such functions of they had the chance.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 06, 2023, 06:47:36 pm
content: As usual, many wrong answers. C has no standard way of inlining functions, andinlinekeyword specifically, and unintuitively, doesnotdo that. There are compiler-specific attributes like always_inline which may help in cases where you want to micromanage compiler's optimization choices.But in reality this is secondary. More important, by far, is to make it a habit to always declare local functionsstatic. C is a weird beast because by default, all functions are exposed to linker. A more sane language design would use a keyword likepublicorinterfaceetc. to expose only interface functions to a module, usually a small subset of functions. In C, you need to do opposite, and remember to use static qualifier for all functions except those that are the interface to your module (compilation unit, .c file).And it is colossally important for compiler to know that this function cannot be accessed from elsewhere, therefore enabling optimizations, including "inlining" the function: inserting the generated machine code at where the function would be called. Compiler does this automagically for astaticfunction when it thinks it's a good idea; adding inline qualifier usually does not affect this decision.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 06, 2023, 06:51:13 pm
content: For example:Code:WriteLED(value) {LATA1= value;}If this was a normal function, the call overhead would make it a lot slower.Inlining inserts the instructions right there, so it's executed instantly, for example inserting ”LATA1=1" when calling WriteLED(1);Inline functions are not really functions in the tradicional way (Call, return), the code will be inserted everywhere it's called, increasing program size, thus normally used only for very short functions.

author: shtirka
author_rank: Contributor
timestamp: November 06, 2023, 06:51:53 pm
content: Quote from: ejeffrey on November 06, 2023, 06:19:01 pmThat may well be true - I will admit that I personally didnt delve into it deep enough even for the 8, 16, and 32 MHz 8-bitters I normally work with since I normally considered that it wasnt a guarantee that you would get a 100% guarantee (in a context of applicability) that youd get a performance benefit for such functions (though I have a sneaking suspicion that my reasoning comes from the fact that I was using the gcc 5.x until relatively recently so didnt really bother to investigate that claim of mine - perhaps time to do a quick test on gcc-12.x and see whats what)Ilya

author: Kittu20
author_rank: Regular Contributor
timestamp: November 06, 2023, 07:04:37 pm
content: I'm a bit confused about why it executes faster compared to the normal function. I've heard about the term 'function call overhead,' but I'm not sure what it exactly means. I've read somewhere that the compiler replace the function call, so I'm wondering, if the function call is replaced, how do we call the function?

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 06, 2023, 07:09:26 pm
content: When you call a function, you have to save the context, place the data somewhere for the function, jump to the function address, then the function must load the data, do its thing, restore the context and return.Inline function avoid all this.This overhead is negligible for a large function, but can make a very small function run 5-10x slower.

author: Warhawk
author_rank: Frequent Contributor
timestamp: November 06, 2023, 07:51:34 pm
content: Quote from: Kittu20 on November 06, 2023, 07:04:37 pmImagine you renovate a house with many rooms. Let's say you do new flooring. Additionally, you have a workshop in the basement with a tool that you can't move (e.g. a tablesaw). In every single room, there are plenty of flooring tiles you need to cut. However, your tablesaw is in the basement. This means that with every piece you need to go to the basement to make the cut. This extra effort is the function call overhead. It is not efficient but you need just one saw.A second alternative is having a table saw in every room. You need more tablesaws but you will be more efficient.PS: Yes, I've done some renovations recently & could not come up with a better example.

author: cv007
author_rank: Frequent Contributor
timestamp: November 06, 2023, 08:11:27 pm
content: Your compiler (XC8) may have different ideas about what the keyword 'inline' means. Its meaning for a compiler like gcc has little to do with actually inlining code (there is an attribute you can specify when you do want inlined code). It has specific purposes, and is probably best to leave it alone until you have a need to use it for a good purpose. Whether XC8 plays by these same rules, who knows.You are at a level where you should concentrate on using 'static' when appropriate. If a function is only going to be used in a single source file, make the function static (same with variables). With that info, the compiler will optimize as appropriate since it knows no other source file will need to access the function/var. Let the compiler deal with optimization, but give it enough basic info like 'static' when appropriate so it can optimize better (it will choose- maybe it inlines the code or maybe it results in a call if used often, either way it will be optimal).Your example code has functions that will not be needed in any other source file, so make them static. The compiler will figure out the best way to optimize. With the XC8 compiler and a pic8 instruction set, it is going to be difficult to start analyzing what is the 'best' way to get the most optimized code (and little need to do so) so just use standard C language best practices and let the compiler do its work.

author: Kittu20
author_rank: Regular Contributor
timestamp: November 06, 2023, 08:12:32 pm
content: Quote from: DavidAlfa on November 06, 2023, 07:09:26 pmIn this scenario, I have a function that takes two integers as input, performs addition on them, and then returns the result. This function is typically defined separately. , I would call this function from main , passing the two integers i want to add as arguments. The function will execute the addition and return the result, which we can use or display as needed.Code:#include <stdio.h>// Function that takes two integers, adds them, and returns the resultint add(int a, int b) {return a + b;}int main() {int num1 = 5;int num2 = 7;int result;// Calling the 'add' function and storing the resultresult = add(num1, num2);printf("The sum of %d and %d is: %d\n", num1, num2, result);return 0;}When we call a function in C, the values of the function's parameters (in this case, a and b) are typically pushed onto the function's call stack, and memory is allocated for these values in the stack frame of the function then , performing the addition of a and b, and storing the result in a temporary location within the stack frame. Once the function completes its execution, the result is typically stored in a another memory location. The memory allocated for the parameters in the stack is released, and the program continues its execution. This is process happens with normal function. What happens if I make this function as inline function

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 06, 2023, 09:22:13 pm
content: Inlining that will make "result = n1+n2", no calls no anything.When enabling optimizations, the compiler will detect this doesn't need to be done, and simply do "result=12".

author: ejeffrey
author_rank: Super Contributor
timestamp: November 06, 2023, 10:26:37 pm
content: Quote from: Kittu20 on November 06, 2023, 07:04:37 pmTwo aspects to this. First is just the overhead of saving context, jumping to a new location, and restoring context when done. Inlining can avoid most or all of that.The second is that it allows all of the conventional compiler optimizations: constant propagation, constant subexpression elimination, loop unrolling, and so on to work across procedural boundaries. A free function has to be implemented to work for any possible caller. An inlined function can take advantage of context specific constraints for optimization.

author: ejeffrey
author_rank: Super Contributor
timestamp: November 06, 2023, 10:42:19 pm
content: Quote from: shtirka on November 06, 2023, 06:51:53 pmAny remotely recent gcc (like; less than 20 years old) will inline whatever it feels like as long as that compiler optimization is enabled regardless of whether you label it with inline. The inline keyword is basically to be used for functions defined in header files. It doesn't force inlining.So it's not guaranteed either way, well formed programs aren't supposed to care beyond performance and memory use.

author: AVI-crak
author_rank: Regular Contributor
timestamp: November 07, 2023, 09:21:09 am
content: When calling any function, an internal convention on the position of passed parameters is triggered. Function parameters can be passed through the stack, through registers, through external memory - there are many variants. However, the general rules are equivalent for all functions.The agreement on passing function parameters - in some cases may generate additional code. Since data always has its own address, moving data to the position of a function parameter is a prerequisite.So, the built-in function does not require moving the data to the position of the function parameter. This is the main advantage of the built-in function. The simple code of the built-in function has a secondary meaning.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 07, 2023, 09:49:11 am
content: Quote from: ejeffrey on November 06, 2023, 10:42:19 pmIndeed. A good rule of thumb: many reserved words in C likestatic,externorinlinehave nothing to do with their names whatsoever, which is highly confusing (especially the re-use ofstaticin totally different meanings). It would be actually better if they were calledapple,bananaandorangeinstead because you wouldn't have to then unlearn the obvious meanings just to re-learn what they mean in C. But this is what we pay for using an ancient language, and one can get used to it.

author: Warhawk
author_rank: Frequent Contributor
timestamp: November 07, 2023, 10:03:16 am
content: I appreciate your insights. I was quite wrong but learnt something new.

author: AVI-crak
author_rank: Regular Contributor
timestamp: November 07, 2023, 02:24:09 pm
content: If you dig deeper, modern compilers are quite clever. They build the code in the best way by themselves. It is very difficult to make the compiler produce assembly code the way you want, and it is almost impossible.The "inline" directive does not have absolute power either, it is closer to a hint for the compiler, which it can easily ignore."__attribute__((noinline))" - absolute noinline."__attribute__((always_inline)))" - is stronger than just "inline", but is powerless in some cases.Using "inline" reduces the inadequate number of "mov" instructions. However, if the function has no parameters, it makes no sense.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 07, 2023, 02:30:57 pm
content: Quote from: AVI-crak on November 07, 2023, 02:24:09 pmEven parameterless function has overhead - you have to jump and return. Granted, the overhead iseven smaller. But in cases where the function does 1 instruction, it is more efficient both performance and size-wise to just copy-paste ("inline") that instruction where it's needed, instead of jumping to a function.

author: ejeffrey
author_rank: Super Contributor
timestamp: November 07, 2023, 05:45:26 pm
content: Quote from: AVI-crak on November 07, 2023, 02:24:09 pmThat's not actually true. Even if a function has no parameters the caller must save any registers that the called function is allowed to clobber, whether it does so or not. It probably has to update the stack pointer. Plus, there is always a jump and return.Of course all of these are small overheads: calling a funciton is fast. But they are not zero. Inlining is especially important in C++ where it's more common to have many layers of wrapper functions and lots of "trivial" accessor functions. Those are considered to be effectively zero runtime overhead because it is assumed that (when compiled with optimization) the compiler will inline the whole stack of them and then optimize down to exactly the few instructions you would have had without the function calls.

author: cv007
author_rank: Frequent Contributor
timestamp: November 07, 2023, 08:08:53 pm
content: inline-instead of either a declaration or definition, you are giving the toolhain a definition (and declaration) that will not trigger a multiple definition error, and allows the options of either 'calling' the function (this function must be callable, and exist somewhere else if ends up being called) or producing a 'copy' inline (it has all the information it needs to do either, as opposed to a definition where the function body is unknown)Code:inline int somefunc(int a){ return a+1; }so this usage is somewhat rare as it requires the function to actually exist somewhere else in case the toolchain decides to 'call' the function, which will depend on how complex the function is and the optimization settings (most usage/misuse like this will work inadvertently, only because the toolchain will most likely produce an inline copy and rarely make a 'call')a more common use is in combination with 'static' (typically in a header, but can be useful in a source file also)Code:static inline int somefunc(int a){ return a+1; }where you are using the 'inline' keyword only to eliminate the toolchain complaining about unused function(s) if the function is unuseda function that lives in a header would typically be 'static inline', where the 'static' makes it local to the translation unit and also allows for it being unused without the toolchain complaining ('tricking' the toolchain into thinking it may be used in another translation unit)- the 'static' is all the toolchain needs to fully optimize its use(s)summary-inlinethere is an external function by this name, either call it or produce the code inline (toolchain's choice) since the definition is known(since the option to call the external function exists, no guarantee you get the inlined version)static inlineuseful for headers, where 'static' does the work of getting the compiler to fully optimize its use(s) and the 'inline' just prevents unused function complaintsThe 'static' keyword is the more useful and more appropriate keyword in most cases. The 'inline' keyword only gives the option for 'static-like', but no guarantee.If using C++, there are other things to say- like implied inline when a class definition lives in a header, inline variables (c++17), etc.As I said previously, use C best practices such as 'static' and 'const' and leave the 'inline' keyword alone until actually required. Outside these best practices, I would not spend a lot of time worrying about whether functions are called/inlined as there are plenty of other more important things to spend time on.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 07, 2023, 08:37:55 pm
content: Quote from: cv007 on November 07, 2023, 08:08:53 pmYes, that's pretty much the only use of 'inline' that serves a purpose, and if you frequently use functions rather than macros for "small tasks", this is something that will come handy.Back in pretty old days, some compilers would actually use it as a hint to inline (and would never inline functions otherwise), just like the 'register' keyword - that was of course not a guarantee, but a hint and compilers would follow the hint if the context allowed. We're talking like compilers from 30 years ago and older.

author: shtirka
author_rank: Contributor
timestamp: November 08, 2023, 07:21:35 am
content: Quote from: ejeffrey on November 06, 2023, 10:42:19 pmActually you may have reminded me that I may have already done a quick experiment on that and found that as you quite rightly point out it actually made no actual difference if I inlined functions or not and inlining was pretty much in the hands of the compiler so to speak. So for what I was doing at the time, quite often there wasnt really much of a guarantee of benefit to have my code inlined

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 08, 2023, 08:06:52 am
content: As a side note, as was discussed recently in another thread, while GCC optimizations (especially starting with -O2, and very aggressively with -O3) will inline a lot of functions automatically, -Os tends to do the exact opposite, which can lead to pretty odd stuff: it tries to factor pieces of code that are repeated into functions and call them.In both cases, the end result may not be favorable in terms of actual performance - the only way to tell is to test, and have a look at the generated assembly code. If it matters, of course. Otherwise just let the compiler do its stuff and move on.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 08, 2023, 09:55:42 am
content: Quote from: shtirka on November 08, 2023, 07:21:35 amFor micromanaging inlining (which is sometimes useful in embedded, if you don't want to go as far as writing assembly, but instead use the "write-C-look-at-assembly-output" or "write-C-measure-with-oscillosscope" strategies), in GCC, just use:#define ALWAYS_INLINE static inline __attribute__((always_inline))ALWAYS_INLINE void set_pin() { ... }I also use this pattern when I refactor something out of function and possibly pass large objects by value (for the code to be most readable), then I feel confident it always works without significant performance penalty. I guess it's still not 100% guaranteed but it has never failed me so far. Note though that if you forcefully inline large functions that are called more than once it will waste significant amount of code memory.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 08, 2023, 10:09:14 am
content: I have a practical example on function inlining in a program, in an effort to help Kittu20 and others to understand what inlining actually is and is not.Let's say you are doing some work that calculates the values of univariate polynomials of form \$f(x) = C_0 + C_1 x + C_2 x^2 + \dots + C_{N-2} x^{N-2} + C_{N-1} x^{N-1}\$, and you don't want to hardcode the values of \$C_k\$ into your C program, and instead read them from a file. Then, you can use a function likeCode:#include <stdlib.h>double poly(const double x,const size_t n,const double c){if (n < 1)return 0.0; // Or NaNdouble result = c;double factor = 1.0;for (size_t i = 1; i < n; i++) {result += factor * c;factor *= x;}return result;}to calculate the value for any given polynomial, given their coefficientsc[]and the value ofxto evaluate it at. For example, to calculate \$f(x) = x^3 + 25 x - 4\$, you'd usen = 3anddouble c = { -4.0, 25.0, 1.0 }(always \$C_0\$ first, \$C_{N-1}\$ last).(In C, youcanprovide the length of an array, if you just do it before the array itself. You can even use multi-dimensional arrays:(const size_t n, const size_t k, const double m)is perfectly valid. For some reason, it seems rarely used. I like to promote it every now and then, because if more programmers were to use it, then the C compilers would more likely grow facilities to report array overruns, giving us programmers a very powerful tool to avoid buffer overrun bugs, the bane of bad C code. The array notation is completely compatible with the pointer notation, and you can supply a pointer todoubleas the third parameter topoly()without any problems, because in C, arrays always decay to a pointer to their initial element. Thesize_ttype, and notint, is also the proper type for sizes.)(Also, while modern C compilers can infer theconst-ness automagically from the code itself, and removing/adding them does not actually cause any change in the machine code generated by current C compilers, theyareimportant ––for us humans. They indicate the author intent, as a promise that "no attempt to modify the values of these variables will be made in this code" (which the compiler will check, and warn or error if violated). Simply put, becausex,n, and the contents of thecarray are markedconst, we humans know their value should stay the same within each invocation of the function, and therefore don't need to spend any mental effort to track them. I use them, because it makes it much easier to maintain code long term for me: instead of looking at the code for a minute, I recognize the function parameterconst-ness, and can parse what the function body does within a few seconds. Other experienced C programmers may be thrown off a bit by the variably-modified array argument, but as I wrote above, I think they too should use it more, instead of the more common pointer notation.)Let's say, however, that youractualtask is to calculate the sum of the values \$f(x)\$ for a set or array of \$x\$ instead. You come up with a function using a very similar interface:Code:double poly_sum(const size_t nx,const double x,const size_t nc,const double c){double result = 0.0;if (nx < 1 || nc < 1)return 0.0; // Or NaNfor (size_t i = 0; i < nx; i++)result += poly(x, nc, c);return result;}Let's assume we write a few tests to verify the code works, and that it indeed does.Now, in the latter function implementation as machine code, does the C compiler generate a call to the previously definedpoly()function, or does it inline the body of that function?The only correct answer is thatthe compiler gets to choose. It may generate a call, or copy the machine code implementing thepoly()function. Even when using the exact same compiler and same optimization settings, it may generate different cases on different hardware architectures. And chaning the compiler settings can of course change that too.Let's say we realize we don't actually need thepoly()function outside the current compilation unit (this C file at hand), and we mark itstaticorstatic inline–– the two are exactly equivalent here ––, i.e. "static double poly(const double x,...". What changes?Withoutstatic, the compiler must still provide object code for thepoly()function even in the case it chooses to inline its implementation within thepoly_sum()function. Thestatickeyword tells the compiler we only use it locally, no external linkage is needed, so it can drop the externally-visible implementation. The compiler is still free to choose whether to generate separate code for thepoly()function, or to inline it within the still externally-visiblepoly_sum()function.All architectures you can compile C code to, have their own function call application binary interface, ABI, or "calling conventions". For example, on 32-bit Windows, when you mark your functions__fastcall, it means they will get their first parameter in the ECX register, and the second in the EDX register. What we call 'function call overhead' therefore usually includes quite a bit of shuffling data from/to registers and stack, since the way functions take parameters and return value(s) is fixed. When the compiler inlines a function implementation, it not only gets rid of the calling instruction, but also all that shuffling of values to/from registers and stack. If the core functionality of the called function is just a few machine instructions, or a short loop like here (on hardware with built-in double precision floating-point math support), the version of the outerpoly_sum()function can end upshorterwith the other function inlined, because many data-shuffling instructions can be avoided. Or not; depending on the exact hardware and ABI, which is why it is a good thing we can leave it up to the compiler to decide on a case-by-case basis.If we were to look at the generated machine code when the C compiler decides to inline the code (at sayCompiler Explorer), we'd also find that when inlined, the compiler also omitted the superfluous check of whethernis zero at the beginning ofpoly(), because the call site,poly_sum()already checked that earlier, and the compiler knows it will never trigger and can therefore omit the code for checking it altogether. Yes, many expert programmers will tell you that that check inpoly()is "unneeded"; I am telling you that because of the auto-inlining facilities of modern C compilers,it does not hurt either. You might hide it behind aNDEBUGcheck or similar, but having that check there is useful, because again, it tells us humans what the author intended when they wrote the function: it is not supposed to return anything useful when the array has zero elements, because then the functions \$f(x)\$ isundefined: it has zero terms!In fact, when the function is marked local-linkage only,static, even when not inlined, the C compiler can note that all call sites have already verified thatnwill be greater than zero, and even though choosing not to inline it, can generate code forpoly()that excludes the explicit check. Nice!However, if you forget thestatic, and think "hey, it's just an extra symbol in the linker which costs nothing, I'll worry about thatlater, for now it might come handy elsewhere", the compiler will NOT be allowed to drop that check, and therefore you'll get machine code that does unnecessary checking (and possibly other unnecessary work), just because you-the-programmer did not want to think about it just now.One key in programming is being as descriptive as you can to the compiler. You do NOT leave things as broad as possible "for now" –– say, because you think you might want to use it from another file later on, and believe it might "save you time" by leaving it exported "for now" ––, and then think you'll restrict them later on as an optimization step, because later never comes. Instead, be as strict and precise and limited as you can be by default, and only relax those when necessary. This ensures the compiler has maximum amount of information available from the get go, and can do those optimization choices the best way possible. Even its warnings about stuff it thinks is contradictory are more useful then. This also means that marking function parametersconstjust by habit, while deemed "unnecessary" by "1337 c0d3rZ", is actually a Very Good Thing.That leaves just the difference betweenstaticandstatic inlinefunctions. For practical purposes, they are identical. Some compilers do warn about an unusedstaticfunction but not about an unusedstatic inlinefunction, which is useful, but not required/suggested/based on the C standard. (Any tricks one might do with external/internal function visibility are better done using compiler extensions, especially via ELF "weak" symbol support.)Personally, I use this to my advantage, by habitually usingstaticfor normal local functions, andstatic inlinefor accessor and helper functions (especially those defined in header files). The compiler does not care, but for us humans, it is another indicator of author intent. If you find you need to expand astatic inlinefunction, you should instead create a new function that calls thestatic inlinefunction to provide the added functionality, because adding extra stuff to an accessor/helper function likely called elsewhere will add unnecessary overhead for those other calls. On the other hand, a normal localstaticfunction is not supposed to do meaningful work, so the consideration shifts to refactoring "is this way of splitting the functionality, intotheselocal functions, the most sensible one?", and only if the answer is "yeah, this still makes sense", you go ahead and add the functionality to the existing function; otherwise you refactor the code, changing how you split the overall task into smaller sub-tasks, and which functions handle which ones.All this means thatwhether the compiler does or does not inline a particular functionis something you do not worry about! What you should worry about is whether you, the human writing the code, have given enough information for the compiler to make good optimization decisions like inlining.Actually, I personally prefer to worry about whether I have given enough information for the compiler toavoid making horribly bad optimization decisionsinstead.It is really not at all important for the compiler to always make the optimal choices, because they are already so good that the difference between "good" choice and "optimal" choice is minuscule. However, horribly bad optimization choices still exist, and those do bite us in the butt: you can lose orders of magnitude of performance by writing "stupid C" which does not allow the compiler to generate efficient machine code, because of the rules.As programmers and developers, we should always concern ourselves most at the algorithm level –– whether our own approach is an efficient one, or whether some other one would work better ––, making sure we implement the code with sufficient specifity and precision, that the compiler can generate good code, or at least avoid generating horrible code.