constant sufixes - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: September 15, 2021, 08:00:31 am
content: I have had two days of fun trying to work out why I could not get the correct result from a calculation in 32 bit envelope on a mega 0 series (8 bit CPU). I am using defines to work things out with various calculations that I expect to be optimized out. But it turns out that writing 5000 does not mean 5000, it's down to what the compiler chooses to interpret it as. In this case possibly an 8 bit number? so gibberish. I solved all problems relating to math errors by running around putting "u" or "ul" on the end of every defined constant.OK so lesson learnt but u means "unsigned int" which it seems in this context is 16 bits. What if I have a 16 bit signed value what do I put? Looking around the net most explanations seem to assume a non 8 bit system that seems to treat everything as 8 bit unless told.What do I do?I could try using constants but then I cannot carry out calculations so easily. I can't calculate a constant that is in turn calculated, the compiler will complain.

author: Jeroen3
author_rank: Super Contributor
timestamp: September 15, 2021, 08:38:34 am
content: In C all numerical literals areintegerordoubleunless otherwise specified. Even hex or octals!You can specify further with suffixes u/U forunsigned, l/L forlong, both UL, ll/LL forlong longand ull for unsigned long long.If it doesn't fit, it will go up one size automatically (adding long)Hence why sometimes ops with 0x80... will give you a warning relating to discarding the sign.To make a float instead of double use the f/F suffix.Example:5000 = integer0x123 = integer5000u = unsigned integer5000l = long integer5000ul = unsigned long3.14 = double3.14f = floatWhatever these types end up being is in inttypes.h

author: Simon
author_rank: Unknown
timestamp: September 15, 2021, 09:55:15 am
content: except I have problems with 16 bit numbers, unless I put u on the end they go wrong. C standards seem to be 16 bit native, I don't know what MPLABX and it's compiler/pre-processor do. I'd rather be explicit. This also helps if I move the high level code to another architecture where int means something different.

author: wraper
author_rank: Unknown
timestamp: September 15, 2021, 10:11:08 am
content: It would be helpful if you post a snippet of your code where the problem happens. Most likely root of the problem lies not in compiler but in your code.

author: Simon
author_rank: Unknown
timestamp: September 15, 2021, 11:16:41 am
content: I'm multiplying numbers, if I don't specify a size for any number over 255 the results go screwy often just spitting out "0". Unless I cast stuff it get's it wrong even though the variable the result goes into is 32 bit and all the figures are worked out so that I do not exceed that limit.

author: Jeroen3
author_rank: Super Contributor
timestamp: September 15, 2021, 01:57:10 pm
content: C does some implicit casting that may be what you're getting bitten by.If you do a multiplication with two variables the result is twice the width. Eg: 2x16bit = 32 bit, 2x32 bit = 64 bit. But if you're doing two literals, the compiler will do them for you.https://godbolt.org/has AVR GCC so you can try to view the assembler live.Can't run it though.Or you are compiling with `-mint8`?https://gcc.gnu.org/wiki/avr-gccTry compiling with-Wconversion -Wsign-conversionto show where conversions are happning.Code:int warning = 255u * 255u;// implicit cast is happening

author: ejeffrey
author_rank: Super Contributor
timestamp: September 15, 2021, 04:09:46 pm
content: Quote from: Simon on September 15, 2021, 11:16:41 amIn C the type of the variable to the left of the assignment doesn't have any impact on the evaluation of subexpressions on the right hand side.When you multiply two numbers they are first implicitly promoted to the wider of the two arguments and at least to 'int'. If the promoted type of the expression is not wide enough to store the result it will be truncated even if it is then assigned to a wider type like a long.In avr-gcc, int is normally 16 bit. That means if you multiply two plain integers that are both over 255, the result will overflow even if you store the result in a 32 bit integer. You need to convert the inputs to long before multiplication if you want to get a 32 bit result.

author: Simon
author_rank: Unknown
timestamp: September 15, 2021, 06:46:26 pm
content: what if I cast the entire sum so32_bit_var = (uint32_t)(var1*var2*var3);

author: golden_labels
author_rank: Super Contributor
timestamp: September 15, 2021, 09:41:35 pm
content: A blind guess: the good old signed overflow while multiplying seemingly unsigned values?Final casting doesn’t affect the calculations. The types, type promotions and everything they entail is resolved at the stage where it is calculated. So in your code the typesvar1andvar2determine what will be the result ofvar1 * var2(and if it’s even defined), and then the output type of that and the type ofvar3determine that again for the next multiplication. And then, finally, the cast touint32(assuminguint32_tis present) is converting the value by either taking modulo of it, wrapping around or doing nothing.If you are worried about having to guess a suffix and that you may choose one that is too large, the good news is that “too large” is rarely an issue with modern compilers, as they are smart enough to optimize out extra calculations, which would not affect the final result after the assignment. In any case, there are(U)INTn_C macros in stdint.h, which generate code with the right suffix.

author: ejeffrey
author_rank: Super Contributor
timestamp: September 15, 2021, 10:51:46 pm
content: Quote from: Simon on September 15, 2021, 06:46:26 pmThat does nothing. The uint32_t is redundant because the product is promoted to 32-bit anyway for the purposes of assignment and it happens too late to change the result of the multiplications.

author: Simon
author_rank: Unknown
timestamp: September 16, 2021, 07:10:09 am
content: Quote from: golden_labels on September 15, 2021, 09:41:35 pmI see, so basically I'd have to put "u" on the end of every constant anyway to make sure I don't end up with half my range. presumably at least one constant needs to be "ul" to promote the calculation to 32 bit.

author: Berni
author_rank: Super Contributor
timestamp: September 16, 2021, 07:26:09 am
content: Yeah some of the compilers for tiny 8bit MCUs really really love working with 8bits in math. Some of the 8bit MCUs are not very good at dealing with large numbers, so since its slow the compiler tries to avoid it.I usually solve it with lots of casting or breaking up longer math calculations into steps as the intermediate variables get optimized away anyway.But i never seen it cut down a constant into a too small data type, it was usually a matter of the calculation working with a too small output type.

author: Simon
author_rank: Unknown
timestamp: September 16, 2021, 07:32:46 am
content: Quote from: Berni on September 16, 2021, 07:26:09 ami think the problem was that I have to cast every constant to make sure the type of math I expect to happen is happening.

author: Simon
author_rank: Unknown
timestamp: September 16, 2021, 07:41:33 am
content: I'm starting to think that I would be better off using a constant initialization instead of a define so that the format is very clear. Or just initialize a variable and let the compiler optimize it to a constant.

author: rstofer
author_rank: Super Contributor
timestamp: September 16, 2021, 02:48:05 pm
content: You could also declare the variable as 'const' and, if it is truly a constant, you probably should. The 'const' declaration may result in the variable being placed in flash rather than RAM, depending on the architecture and the compiler.

author: ejeffrey
author_rank: Super Contributor
timestamp: September 16, 2021, 03:06:54 pm
content: Quote from: Berni on September 16, 2021, 07:26:09 amIt has nothing to do with that. The C semantics are very clear on integral promotion once the type mapping ABI is chosen. The compiler will faithfully execute what you write regardless of it's opinion as to the performance of the architecture.@Simon: yes, static constant int is often a better choice than #define constants. The compiler will generally optimize them away completely as long as you never use the address-of & operator on them. In C you can't use them everywhere because variables, even const variables can't be used as array length specifiers but for arithmetic you should be fine.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 16, 2021, 04:40:53 pm
content: Quote from: golden_labels on September 15, 2021, 09:41:35 pmSounds like a good idea for portability reasons. Now of course this is extra typing.But mixing stdint types with legacy suffixes such as U or UL is not a good idea.

author: Jeroen3
author_rank: Super Contributor
timestamp: September 16, 2021, 07:19:18 pm
content: Quote from: Simon on September 16, 2021, 07:32:46 amIf your doing this, and you want anunsigned subtractto happen then you must make it 5u, yes.Code:unsigned int var;int ans = var - 5;Typically what I do it I don't put constants in expressions.Code:uint16_t a;uint16_t b;uint32_t c = a * b;Doesn't leave much room for imagination.So you going around telling everything to beul, may not be your intended result due to what choice of (slow) multiply it picks.TI has this short note describes it, for your archiving:https://www.ti.com/lit/an/spra683/spra683.pdf

author: ejeffrey
author_rank: Super Contributor
timestamp: September 17, 2021, 02:49:57 am
content: Quote from: Jeroen3 on September 16, 2021, 07:19:18 pmYou say that but the whole point of this conversation is that this code is defective and will fail on 8 and 16 bit platforms.

author: golden_labels
author_rank: Super Contributor
timestamp: September 17, 2021, 11:28:41 am
content: Note: this has nothing to do with optimization. Optimization doesn’t change semantics of the code.(1)At most it exposes errors in already invalid code, that might’ve been missed in other circumstances.Quote from: Simon on September 16, 2021, 07:10:09 amThat depends on the intention. A multiplication of twounsigned intvalues will be done entirely withinunsigned int. For most of the inputs it will overflow. An unsigned overflow is well defined in both C and C++, but it may not be what you want.Quote from: Jeroen3 on September 16, 2021, 07:19:18 pmExcept that this code shows exactly what has been explained to be wrong: both in this thread and the document you have yourself linked.uint32_tin the third line bears no significance for the multiplication. That multiplication is determined by the types ofaandb, which areuint16_tin this case. Depending on the platform that may besignedmultiplication and, again depending on the platform, that may overflow.If you ever used this code and it was giving the results you expected, it’s because of the lucky circumstances. Typicall this is a combination of working with 2’s complement representation of integers and compilers optimizing undefined cases. Since a compiler is allowed to do so, it substitutes that with code acting as if the operation was valid (which is more optimal). By magic of 2’s complement the intermediate result, despite being actually invalid, has binary representation matching what it would be if the whole operation was unsigned. And since the code downstream re-interprets it as unsigned, it happens to be what you expected.But this is an accident. This is not what this expression indicates in terms of language’s semantics.____(1)The exception being bugs in the compiler itself, but those are very rare compared to mistakes of whoever uses the compiler.

author: Simon
author_rank: Unknown
timestamp: September 17, 2021, 12:37:38 pm
content: so I either create the variables to be the same size as that which they will be multiplied into or I have to cast each one at a time in any multiplication.

author: Jeroen3
author_rank: Super Contributor
timestamp: September 17, 2021, 12:44:19 pm
content: Quote from: ejeffrey on September 17, 2021, 02:49:57 amFascinating, I've learned from year 1 C that a multiply always ends up with a result twice input width.How have I not been bitten by that in all those years?Seems it doesn't work right only on targets that don't have a 16 bit multiply. It will call you a library.msp430: int16 __mspabi_mpyi (int16 x, int16 y)avr: __mulhi3 ;;; (C1:C0) = (A1:A0) * (B1:B0)Can't check tasking 8051 now, since that's the only 8 bitters I've worked with.While on ARM, PowerPC or x86 it promotes them first to 32 bit, while it could very well have used the 16 bit multiply instructions....

author: Jeroen3
author_rank: Super Contributor
timestamp: September 17, 2021, 12:47:19 pm
content: Quote from: Simon on September 17, 2021, 12:37:38 pmFrom what I did just now it looks like at least one of the multiplication terms needs to be the size you want the result the be in. Be it via a cast,type suffix, or being the actual type.

author: ejeffrey
author_rank: Super Contributor
timestamp: September 17, 2021, 02:34:03 pm
content: Quote from: Jeroen3 on September 17, 2021, 12:44:19 pmAll that matters is whether the platform is natively 16 or 32 bit: i.e., what is the size of an unqualified int. The C standard promotes integers in arithmetic operations to 'int' because that is assumed to be the most efficient size for arithmetic operations. So your code will work fine on 32 bit platforms due to that implicit promotion. However it won't work on 8 or 16 bit platforms and the version that does 32*32 -> 64 bit won't work on any standard system.Note that this is a required part of the standard and is done regardless of whether it is actually more or less efficient on any given hardware. It is just the integer promotion rules that the C standard specifies.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 17, 2021, 05:44:05 pm
content: Quote from: Jeroen3 on September 17, 2021, 12:44:19 pmIt's unfortunately not that fascinating, as C arithmetics is very poorly taught, and very poorly talked about in most books. The rules are "clear" in the standard, but not that many developers really master them.Certainly, the result of a multiply of N-bit x N-bit requires 2N bits to be represented in the general case, but C doesn't care about that. Thing is, C always favors efficiency, so it uses simple rules that do mostly favor efficient compiled code, rather than ensure "implicit correctness" in the math sense.While that allows efficient code in most situations, I admit that C arithmetics is one of the most awful things ever.Quote from: Jeroen3 on September 17, 2021, 12:44:19 pmMaybe a mix of luck and the use of some compilers that would not strictly follow the standard, to your benefit. Who knows.

author: golden_labels
author_rank: Super Contributor
timestamp: September 17, 2021, 06:25:00 pm
content: Quote from: Simon on September 17, 2021, 12:37:38 pmIf you want to obtain the full result, then at least one of the operands should be of type that can hold it: either naturally, casted or for other reasons. But keep in mind that this doesn’t imply it must be always twice the size. If you are multiplying twouint_fast16_in the range of [0, 10], they will fit even into the smallest possiblesigned char.Quote from: Jeroen3 on September 17, 2021, 12:44:19 pmLikely for the reasons explained in my previous post.

author: hamster_nz
author_rank: Super Contributor
timestamp: September 18, 2021, 09:35:54 am
content: I might be getting the new twist in this thread wrong....Take this code on a 32-bit platform:Code:#include <stdio.h>#include <stdint.h>uint16_t my_func(uint8_t a, uint8_t b){return a*b ;}Called with "my_func(20,20)" I can't work out if people are suggesting it should be 400 or 144.My understanding is that the required behavior is smaller types are first converted to the native signed or unsigned integers, the calculation is then carried out, and then the result is truncated during the assignment.This also agrees with the generated code:Code:push rbpmov rbp, rspmov edx, edimov eax, esimov BYTE PTR [rbp-4], dlmov BYTE PTR [rbp-8], almovzx edx, BYTE PTR [rbp-4] ; Promote 8-bit unsigned value to 32-bit unsigned valuemovzx eax, BYTE PTR [rbp-8] ; Promote 8-bit unsigned value to 32-bit unsigned valueimul eax, edx ; 32-bit multiply to give result in eax.pop rbpretLikewise all floating point calculations are carried out as doubles, unless you override this with a compiler switch.

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 18, 2021, 01:44:27 pm
content: In arithmetic expressions, if the value of an operand can be described as an int, it is promoted to an int; if it is smaller than an int but cannot be represented by an int, it is converted to an unsigned int. All other integer types (i.e., those with range at least as large as int or unsigned int) are kept unchanged. (See e.g. C99 6.3.1.1.)Thus, all operands in an integer arithmetic expression are always at least int or unsigned int, or larger integer types.Arithmetic operations (like addition and subtraction) cause additional conversions (see e.g. C99 6.3.1.8). Basically, floating-point and integer operands are promoted to the type of the larger operand. (Technically, floating point type ranking isfloat,double, andlong double; integer type ranks are listed in e.g. C99 6.3.1.1p1.)This means that if you define your ownQ15fixed point format, you can write the multiplication operation (A×B×2-15) asCode:#include <stdint.h>typedef int32_t q15;static inline q15 q15_mul(const q15 a, const q15 b){return ((int_fast64_t)a * b) / INT32_C(32768);}Usingreturn ((int_fast64_t)a * b) >> 15;will work, but is technically "implementation defined". (I personally do use it, because it is common enough so that any implementation that fails to generate equivalent code, will silently miscompile a ton of of existing C code anyway.)Becauseaandbare 32-bit quantities, we want the intermediate result to be 64-bit, before the division/shift. To do this, we need to cast one of the operands to a 64-bit signed integer type, hereint_fast64_t. The compiler will promote the other operand(s) to that type also, due to the arithmetic operations.TheINT32_C(32768)is simply a way to write the 16-bit divisor without worrying about the maximum range of anint. Written this way, we leave the compiler to promote it toint_fast64_t. All C compilers currently used will optimize the division to a bit shift, on all architectures where that works (all GCC supports).We could explicitly cast all three operands toint64_torint_fast64_t, but the last time I checked the behaviour of various C compilers a decade ago, letting the compiler to do the type promotions, let them generate better optimized code: fewer register moves instatic inlinefunction, especially on LP64 architectures like x86-64 on Linux.Writing your own mixed-size fixed point arithmetic operations (in assembly, e.g. GCC or Clangextended asm) on types larger than the native word size, can save a significant number of operations, because (at least the last time I checked), compilers typically use a compiler-provided functions to do the operations, without optimized mixed-size versions. (A particular example is on 8-bit architectures, converting anN-bit unsigned integer to decimal via repeated multiplications by 10, and subtracting the smallest power of ten at least 2NusingN+8-bit unsigned integer type.)

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: September 24, 2021, 05:37:13 am
content: Quote from: Nominal Animal on September 18, 2021, 01:44:27 pmWhy not fewer register moves inanyfunction? What is the importance of `static inline` in this context, especially considering that `static inline` is always redundant and 100% equivalent to plain `static`.

author: Berni
author_rank: Super Contributor
timestamp: September 24, 2021, 07:15:28 am
content: A static function in C just means that the function is only reachable from within that one file.But "inline" has a completely different effect. Normally the compiler places a function somewhere in memory and jumps into it when needed, but inline tells the compiler to instead just copy paste the whole function code into program memory on the spot where you call it. This not only saves the time required to jump in and out of a function but allows the compiler to additionally optimize the function for that particular use case (like if a parameter is being passed a constant value that causes parts of the function to never execute). This can be a big performance boost for functions that get called very often, but can also consume a lot of extra code memory.But not making a function inline doesn't mean it will never get inlined. The compiler might still decide it is worth inlining for very short functions. Making it inline just forces the compiler to do it every time, because you deem the performance gain to be worth the code size sacrifice.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 24, 2021, 04:36:54 pm
content: There is semantics and then there is actual compiler behavior.'static' is only meant to make a declaration (be it functions or global variables, for local variables, that's a bit different yet) only visible to the current "compilation unit" (well, "file" is not completely exact, because a compilation unit can be made of several files with included files...), while 'inline' (at least in C) is just ahint to the compilerthat you'd the like a function to get inlined wherever it's called - to be more exact, actually, that it must be fast to call, the method to this end not being defined. There is no guarantee a given compiler will honor this though. Likewise, a given compiler may absolutely inline functions that you never qualified 'inline'.Source:QuoteIn particular, and that's where there's a link between the two, many C compilers, above a certain optimization level, will inline 'static' functions when they determine it's worthwhile, while never generating a callable function for them - since they are not callable from anywhere else.But compilers can also inline functions in a given compilation unit that are neither qualified 'static' or 'inline'. In that case, they will usually both inline said functions in the same compilation unit - if they determine it's worthwhile - and generate the code for a callable function, since it may be called from outside of the compilation unit.Only some compiler extensions can guarantee that a given function will get inlined for sure. That's not standard.

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: September 24, 2021, 05:54:35 pm
content: Quote from: Berni on September 24, 2021, 07:15:28 amAbsolutely not. Keyword `inline` has no direct relation to "telling the compiler to instead just copy paste the whole function code". It is true that back in the dark ages this keyword was somewhat "planned" to serve that purpose, but the idea was laughed out of existence virtually instantly. It has never been that way.Keyword `inline`does nottell compiler anything about inlining the function code. It does not request inlining. It is completely unrelated.The purpose of keyword `inline` is very simple and very focused: keyword `inline` allows you to circumvent the restrictions of the dreadedOne Definition Rule (ODR). Keyword `inline` allows you to make multiple definitions (!) of the same entity withexternal linkage(!) in your program, and at the same time keep the linker happy (i.e. prevent the linker from barking a "multiple definition" error). In C++ keyword `inline` is equally applicable to functions and variables for that purpose.One might ask: why would anyone want to provide multiple definitions of the same external-linkage entity?The are many valid reasons for that, which involve such things as header-only libraries, template metaprogramming, constant substitution, bla bla bla, etc etc etc. It is a very convenient feature.But one of those reasons is, not surprisingly: to facilitate function inlining. In order to inline a function call the compiler has to see the entire definition of the function from the point of the call. Which means that if you want a global function to be inlinableeverywhere, the full definition of the function has to be visible ineverytranslation unit. But doing it literally that way for a function with external linkage will immediately trigger ODR violations. I.e. the linker will kill you for trying that. And this is where keyword `inline` comes to the rescue. In simple words, keyword `inline`suppressesODR violations. It tells the linker: "if you encounter multiple definitions of the same entity, just keep your mouth shut, quietly choose and keep just one definition". That all keyword `inline` does.Now, whether the compiler will decide to actuallyinlinea function call (or not) depends entirely on its internal decision criteria. Keyword `inline` has no bearing on it whatsoever. Keyword `inline`indirectlyhelps tofaclilitateinlining, but does not trigger or request inlining in any way, shape or form (contrary to a popular misguided belief).Now, if you paid attention, above I'm talking specifically about entities withexternal linkage. But what about entities withinternal linkage, like freestanding `static` functions? Well, for entities with internal linkage the aforementioned problem simply does not exist at all. The aforementioned ODR issue simply does not apply to entities with internal linkage. Functions with internal linkage arealwaysdefined in their respective translation units. Their full definitions arealwaysvisible to the compiler. Which means that for them inlining isalreadyfully facilitated by the mere fact that these functions have internal linkage. There's no need to do anything extra.This is the reason why `static inline` is always redundant for a freestanding function or variable. `static inline` is equivalent to plain `static`. Once you declared a function as `static` you have already done everything you could to facilitate its inlining. Adding `inline` on top of it will not achieve anything extra.End of story.(The above is written with C++ in mind. The semantics of `inline` with external linkage functions is different in C. But when it comes to `static inline` the semantics is pretty much the same. `static inline` is exactly as redundant in C as it is in C++ for exactly the same reasons.)Quote from: SiliconWizard on September 24, 2021, 04:36:54 pmNo, it isn't. This "old wives' tale" just refuses to die. There's no "hint" of any kind in `inline`.In C, just like in C++, `inline` is nothing more than a "ODR circumventer": a way to marry external linkage with multiple definitions.

author: Jeroen3
author_rank: Super Contributor
timestamp: September 24, 2021, 06:48:08 pm
content: inline never has the effect you are looking for. It's better to use the compiler specific options if you want the literal effect of the keyword, like:__attribute__((always_inline));or__forceinline.Yes, you lose portability. But when you need to do this you probably are micro-optimizing anyway.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 24, 2021, 07:15:21 pm
content: Quote from: TheCalligrapher on September 24, 2021, 05:54:35 pmI quoted the f*cking standard as a source. And added that what it said was more about the call being "fast" than inlining, which you omitted here.Apparently, your own tale is better than standards.

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 24, 2021, 11:52:55 pm
content: The key reason why I mark macro-like accessor functionsstatic inlineas opposed to juststatic, is that GCC does not issue a warning for unused functions of the former type, while they do for the latter, when default/recommended/common warnings are enabled. In other words,static inline: Accessor-like function, okay if not used at all. Only defined in this compilation unit; does not generate a linkable symbol in the symbol table. If not used, the object file won't implement the function at all.static: Local function. The compiler should warn, if not used at all. Only defined in this compilation unit; does not generate a linkable symbol in the symbol table.Neitherstaticnorinline: Externally accessible function (part of API, generates a linkable symbol in the symbol table in the object file).Note that these are all at least as much directed to my fellow developers as they are to the compiler; and do not make any assumptions about whether the function is actually inlined by the compiler or not.Anyway, no need to listen to me or anyone else, when you can verify the facts for yourself. Take for example the followingexample.c:Code:#include <stdlib.h>#include <stdio.h>#undef FUNC_PREFIX#if defined(USE_STATIC_INLINE)#define FUNC_PREFIX static inline#elif defined(USE_STATIC)#define FUNC_PREFIX static#elif defined(USE_INLINE)#define FUNC_PREFIX inline#else#define FUNC_PREFIX#endifFUNC_PREFIX void describe(const int num, const char *val){printf("%d: %s\n", num, val);}FUNC_PREFIX int unused_function(int num){return num + 1;}int main(int argc, char *argv[]){for (int i = 0; i < argc; i++)describe(i, argv);return EXIT_SUCCESS;}and compile the four versions (I will be using -O2 because that's my habit, but do check other opimization options as well as omitting it):Code:gcc -Wall -O2 example.c -o ex.nonegcc -DUSE_STATIC -Wall -O2 example.c -o ex.staticgcc -DUSE_INLINE -Wall -O2 example.c -o ex.inlinegcc -DUSE_STATIC_INLINE -Wall -O2 example.c -o ex.static_inlineOn my system, the-DUSE_STATICcauses the compiler (GCC 7.5.0) to complain about unused_function() being defined but not used.(Clang does complain for both-DUSE_STATICand-DUSE_STATIC_INLINE, though.)The above example is too simple to exhibit any code changes. It always triggers the compiler logic on when to inline a function. That is, all run the same code, but onlyex.nonecontains contains the binary symbolsdescribeandunused_function. Feel free to investigate your own functions (my own focus was in funky complicated double-precision arithmetic functions and basic 3D vector algebra operations) to see ifyourcode tends to be affected the way I described in my earlier post.Although GCC code generation has improved a lot since the GCC 2 (1992-2001) and 3 (2001-2006) era, GCC 4 still generated a lot of superfluous register moves, increasing register pressure, and often using stack for temporary variables. This was particularly noticeable when inlining a function (which can occur with or without declaring the functioninline).If you are interested in how GCCstatic inlinehas evolved, compare4.0.4to7.5.0tolatestGCC version inlining documentation. As described in various versions,static inlinehas similar semantics in both C and C++, which is very useful when working on microcontrollers (that rely on a funky mix of freestanding C and C++ environments).In short, the reasons I personally mark a functionstatic inlinehas nothing to do with inlining per se, and everything to do with my intent regarding that function; especially whether it is okay for it to be completely omitted from the compiled binaries (i.e., not used/needed at all).Asking myself:Okay, but how that relates to your statement that"let them generate better optimized code: fewer register moves instatic inlinefunction"?About a decade ago, I had access to GCC (4.x.y), Intel Compiler Collection, Pathscale, and AMD Open64 C compilers on Linux; that's when I did those experiments on x86 and x86-64 to see the effects on the generated code.The understanding I developed from testing the abovementioned compilers (and ignoring "no change either way" cases; thus not trying to get the best results for a specific compiler, but to avoid the worst cases regardless of compiler), was that implicit and explicit casting are done at different stages of code synthesization, and that implicit casting makes it easier for the compilers to realize a register is unused, or always filled with zeros –– for example, when casting a 32-bit or smaller value touint64_ton a 32-bit architecture. When the code is in a smallish local scope, say a macro-like accessor function, or a pure arithmetic function, this was more noticeable. Obviously, this only matters when these expressions are heavily used in a program; I was dealing with potential models in simulations, calculated hundreds of millions of times per second.The best way to explain this, is to compare the following code (a×b/232) compiled for 32-bit Cortex-M4 and Cortex-M0:Code:#include <stdint.h>int64_t mul64q32(const int64_t a, const int64_t b) { return a*b >> 32; }int32_t mul32q32(const int32_t a, const int32_t b) { return ((int_fast64_t)a * b) >> 32; }Compiling these to Cortex-M4 on GCC-7.5.0 yields (essentially)Code:mul64q32:mul r3, r0, r3mla r1, r2, r1, r3umull r2, r3, r0, r2adds r0, r1, r3asrs r1, r0, #31bx lrmul32q32:smull r0, r1, r0, r1mov r0, r1bx lrBecause of the 32-bit shift, one of the four 32-bit multiplications can be omitted in mul64q32. Cortex-M4 has 32×32bit multiplication with 64-bit result (in a register pair), so a single operation suffices. Ifmul32q32gets inlined, and the surrounding code can use the result directly in ther1register, themovcan be avoided, too: it then simplifies to a singlesmullinstruction.Now, compile the same code for Cortex-M0, and we getCode:mul64q32:push {r4, lr}bl __aeabi_lmulmovs r0, r1asrs r1, r1, #31pop {r4, pc}mul32q32:movs r2, r1push {r4, lr}asrs r1, r0, #31asrs r3, r2, #31bl __aeabi_lmulmovs r0, r1pop {r4, pc}where__aeabi_lmulis a compiler-provided 64×64-bit multiplication with 64-bit result (r1:r0 × r3:r2 = r1:r0).Because the ARM GCC implementation on Cortex-M0 does not have a 32×32-bit multiplication with 64-bit resultas a compiler-provided function, it has to expand the multiplicands to 64 bits, and use a generic 64×64-bit multiplication function. (Clang-10 does the same, using__muldi3function, but does a funky shuffle to swap the two register pairs - essentially five unnecessary register-to-register moves. Odd.)The root problem here is not at all in inlining or anything related to that, but theprematurepromotion of arguments to a multi-word type, then using a generic but slower operation to do the arithmetic (because the compiler does not realize it can simply omit doing the superfluous operations).This seems to still be an issue, so much so that if using GCC or Clang-10 to compile for Cortex-M0, it would be worth the effort to implementmul32q32in inline assembly, since it would need only two multiplication instructions, compared to four in __aeabi_lmul/__muldi3, assumingmul32q32was so heavily used the difference would matter in real life. (Personally, I implement both [inline assembly and naïve-but-easily-verifiably-correct versions], selectable at compile time, with runnable unit tests on the target to verify they produce identical results for all arguments.)As I always say, reality beats theory. Here, it means that while the C (and C++) standards describe the rules that should yield portable code (for example, "correctness"), individual compilers have behaviours ("efficiency") we can examine and rely onbecause of practical reasons. Yes, it does mean that before one can rely on these features, the output of each new (major) version of ones compiler has to be checked.Simply put, standards describe "correctness", whereas "efficiency" is up to individual compilers. If you want the latter, you need to examine how different compilers generate efficient code.In my experience, the key point is actuallynotoptimum code generation, but to try and avoid the silliest and worst cases instead. (A good example of this is how optimizing for size,-Os, can often yield as efficient code as-O2or even better. Then, the efficiency gained is just a side effect of trying to keep code size down.)When you have something likemul32q32above, used millions of times a second, implementing it in assembly for specific architectures is often worth the effort; you only know after examining the code generated by your toolchain for that particular architecture. You basically sidestep the compiler altogether by switching to assembly, instead of trying to find the best C or C++ expression for the job. (On x86-64, one can use<immintrin.h> intrinsicsfor Single-Instruction-Multiple-Data operations, instead of resorting to assembly. This was my main observation on x86 and x86-64 with floating-point math, really; and not relying on the compiler to vectorize the expressions also means that one has to think of data ordering and access patterns, which makes a major difference wrt. efficiency with SIMD.)

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: September 25, 2021, 04:06:04 pm
content: Quote from: Nominal Animal on September 24, 2021, 11:52:55 pmThat's a rather naive and self-contradictory statement.In order to "verify the facts oneself" one needs to be able to properlyinterpretthe results obtained from the experiments with specific compilers. Which implies an extremely high level of theoretical expertise in the language concepts and rationales behind these concepts. And that in turn entails either spending those long winter nights buried neck-deep in language-related documentation (it you want to eventually cover the whole thing) or devotedly listening to me and committing every word I say to memory (if you want an expert explanation focused on a specific topic).Without it, these attempts to "verify the facts oneself" will only lead to rathersilly, blindly misguided conclusions, as we all perfectly well know.But even in the best case, quirks of a specific compiler are just that. And most of the time they are just consequences of the fact that some users don't even bother to learn to use these compilers properly.

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 25, 2021, 08:40:03 pm
content: Quote from: TheCalligrapher on September 25, 2021, 04:06:04 pmYes, but only to the extent of comparison, and detecting the worst cases; definitely not down to estimating instruction timings and such. Remember, the key is not to detect the optimum or analyze anything at high precision; it is to detect if something produces superfluous codecompared to other options. The goal is not to get the optimum on a specific compiler version, but to keep results acceptable. (When I want optimum, I implement architecture-specific function variants in assembly, and auto-choose at compile time usingPre-defined Compiler Macros(Sourceforge project); usingextended asmfor the most of astatic inlinefunction body lets GCC and Clang inline (or not) the code, saving register moves, and even pick better (different!) registers used in the inlined part in different use cases.)As I often say: Opinions are not that interesting, but the reasons and facts behind them are. I am attempting to show others how to re-discover those facts by oneself, especially because it has been a significant time (in GCC and Clang development terms) since I spent the few dozen hours myself.The most useful tool for this kind of analysis (in addition to-Scompile option flag, which causes the compiler to generate assembly.ssource files from the C or C++ source files) in a typical GCC/Clang toolchain isobjdump; the object file examination tool. The key options are-tList symbols in the symbol table (usable across compilation units)-TList dynamic symbols (if dynamically linked binary-dDisassemble (show assembly of object code)In addition, you need to understand the basic features of the assembly code on the target architecture. For example, on AVRs, most instructions are one of two cycles (seeAtmel AVR Instruction Set Manual (PDF)) and since there is no instruction cache or speculative execution or other stuff complicating things, the assembly is not difficult to read: comparing two functionally equivalent assembly snippets at the"is one of these silly, much larger or slower than necessary?"level, does not require much expertise.Even in the Arduino environment, if you bother to find out the locations for the Arduino AVR-GCC installation and the sketch temporary directory locations, you can useavr-objdump(with above option flags) to examine the user-code object files. If one copies the snippets (removing the address part) to separate text files,diffutils(in particular,diff -Naburfile1file2) provide excellent tool for comparisons.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 26, 2021, 01:42:51 am
content: Experimenting with compilers is not unreasonable when you're dealing with features that areimplementation-definedas per the standard. Now whether one is qualified enough to interpret the output of a given compiler - that may not be trivial indeed, but let's not insult people. If you attempt to do this, chances are you at least half know what you're looking at.Whether it's worth bothering about what is implementation-defined, it's yet another matter. It should usually *not* matter if you're only writing 100% portable code, but otherwise, it's often useful, or sometimes necessary.Now I admit getting a given compiler's implementation for a given language feature from the documentation of said compiler would be better than trying to "reverse-engineer" it. But this kind of documentation is not always available, or if so, not always accurate or up-to-date.Let's see what GCC's docs say about 'inline' functions, for instance:https://gcc.gnu.org/onlinedocs/gcc/Inline.htmlUnfortunately, it doesn't seem really up-to-date. But should still give you a general idea of how GCC deals with 'inline'.

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 27, 2021, 08:55:03 am
content: Quote from: SiliconWizard on September 26, 2021, 01:42:51 amAlso, the only implementation-defined part in my examples is the right bit shift of signed values. The rest is abouthow the compiler implements the behaviour, not about analyzing the implemented behaviour. I'm only suggesting looking at the assembly tocompare functionally equivalent codeto see if one isunacceptably stupid or complicated; not even to see if one is preferable over the other.

author: Jan Audio
author_rank: Frequent Contributor
timestamp: September 30, 2021, 03:25:05 pm
content: Simon, just take a value type that fits your calculation;signed long i = yourbyte;i += yoursignedchar;i -= moreofthesame;With XC8 it takes yourbyte and works no further then 0..255 if you code like :signed long i = yourbyte + yoursignedchar - moreofthesame;All compilers work different, this understands all.

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: September 30, 2021, 04:19:20 pm
content: Quote from: SiliconWizard on September 26, 2021, 01:42:51 amThe description is not as much "out of date", as it is embogged in historical GCC's idiosyncrasies. GCC jumped the gun, made an attempt to introduce its own idea of `inline` before it was standardized by C99, and eventually failed miserably. Basically, the primary outcome of that attempt was a big mess. The page at your link is a rather cursory description of that mess. I don't see any reason to waste time on that, unless one's trying to do some archeological research on old code.In order to properly understand compiler-specific behavior, be it presented in form of compiler-documentation or in form of translated code sample, one has to be able to properly immerse that compiler-specific behavior into the context of abstract requirements of the language. Compilers do follow those requirements, no way around that.But anyway, this all looks like a smoke screen that can only obfuscate a rather specific issue I was originally commenting upon: the proper idea of `inline` in modern C and C++ immediately and invariably entails the understanding that `static inline` is redundant and fully equivalent to plain `static`. (Which, not surprisingly, the above experiments with the compiler fully confirm.)

author: Nusa
author_rank: Super Contributor
timestamp: September 30, 2021, 05:15:00 pm
content: Quote from: Simon on September 15, 2021, 08:00:31 amUmm...you do realize that #defines are macros that result in literal TEXT substitutions in the context you're using them in, and are generally not numerically evaluated by the preprocessor until they are referenced in a line of code aka the context? We really need to see your specific example (#defines and context) if you want specific feedback.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 01, 2021, 05:20:28 pm
content: Quote from: Simon on September 15, 2021, 08:00:31 amThe most portable way to define the size of a literal integer constant (like 5000) is to use one of theNAME_C(literal)macros as defined in the compiler-provided<stdint.h>. (This header file is available even in freestanding environments where the standard C library (like<stdio.h>and others) are NOT available. Specifically:INT8_C(-128...+128)UINT8_C(0...255)INT16_C(-32768...+32767)UINT16_C(0...65535)INT32_C(-2147483648...+2147483647)UINT32_C(0...4294967295)These evaluate to the literal itself, with the appropriate suffix (u, uL, LL, uLL, et cetera) appended, taking the guessing completely out.This also means you cannot do e.g.UINT8_C(NAMED_CONSTANT). Use a cast,(uint8_t)(NAMED_CONSTANT), for those instead.Again, these are provided by the compiler (and must be provided by the compiler since C99 or so, and are provided even by Microchip XC compilers), and do not depend on any standard C library being available.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 01, 2021, 06:47:32 pm
content: Quote from: Nominal Animal on October 01, 2021, 05:20:28 pmYes, yes. That has even already been suggested earlier in the thread. Now I guess it needs to sink in.Now, as we discuss on a regular basis, is C a bit tricky for arithmetics? It is. Comes both from legacy and efficiency reasons.Regarding literals, and otherwise potential conversion issues, there is a GCC warning that is very useful and that I always enable, especially for embedded development: '-Wconversion'. Enable it!A quick example (using large values just because I tested it with GCC for x86_64):Code:int Test(int n){int m = 20000000000 * n;return m;}The literal here is 20.10^9 - it exceeds what can be represented as an 'int' in this context.With 'gcc -Wall': no warning.With 'gcc -Wall -Wconversion':Code:test1.c: In function 'Test':test1.c:3:10: warning: conversion from 'long long int' to 'int' may change value [-Wconversion]3 | int m = 20000000000 * n;| ^~~~~~~~~~~I really suggest the OP test this warning option with their original code, and report back if it indeed catches the issue. It should.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 01, 2021, 07:35:58 pm
content: Quote from: SiliconWizard on October 01, 2021, 06:47:32 pmTrue; in my defense, the explicit forms (type_C()macros) only in my own post, and I thought that listing them in a post-it note-worthy form would be useful.Quote from: SiliconWizard on October 01, 2021, 06:47:32 pmDefinitely!To me, it is an important tool on microcontroller targets with less common sizes of the base types (short,int, andlong). I often only realize I need to wrap a constant in one of the abovementioned macros from the compiler warning...In addition to the above, let me repeat that when an operation produces an intermediate value that can exceed the range of the operands (remembering that the C compiler already promotes the operands toints automatically, and if even larger, the smaller to the larger operands type), one only needs to explicitly cast (via(type)variableor(type)(expression)) one of the operands, logicallybeforethe operation is done. In particular,a = (type)(b*c)orconst int64_t a = b*c;only promotes the result, and is up to the compiler whether the result is limited to the range and precision ofbandc. This bug is so common that on x86-64, at least GCC provides the 64-bit result; I consider this bad, because on other architectures and compilers, it can differ and often leads to very frustrating bugs. For correct code, you need to do e.g.const int64_t a = (int64_t)a * b;instead.Let's say you have 32-bit variablesa,b, andc, and you want to calculate(a+b)*c, but you know thata+bwill always fit in a 32-bit integer (for some external reason). My earlier text basically attempted to explain why in that case(int64_t)(a+b) * cor(a+b) * (int64_t)cwill work. If the sum does not necessarily fit in a 32-bit integer, then you should use for example((int64_t)a + b)*c, so thatbandcare automatically promoted toint64_ttoo, by the compiler. (Other expressions will also work, for example(a + (int64_t)b)*c.)Taken all together, this can produce quite verbose-looking code with(int64_t)(...)andINT64_C(...)mixed in. Do not let it bother you; such verbosity is sometimes preferable. And not just for the compiler; it can help us humans too, as reading new code with such notation, even when verbose and on long lines, makes the programmer intent/understanding (of valid ranges for that expression) easier to see and understand. Code golf may be fun, but it is definitely not practical in real life.Adding good comments, describing the math formulae, operand/variable ranges, and programmer reasoning why exactly this implementation was chosen, makes such codeexcellentin my opinion, even if long and somewhat arduous to unravel. (The opposite, "nasty" code, is brief code that has "clever tricks" hiding in the implementation, unmentioned anywhere, hard to notice, and are the linch-pin for the implementation to work correctly.)

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 01, 2021, 07:44:34 pm
content: Quote from: Nominal Animal on October 01, 2021, 07:35:58 pmI agree. And this is all due to the fact C needs to remain the most efficient possible in most situations. So you need to "instruct" the compiler what it is you really want it to implement.Sure some could dream of easier arithmetics, not needing to care about types and conversions, and with all expressions always being mathematically correct in all cases; that would almost invariably lead to much less efficient code in general. If you expect that, use another language.

author: blacksheeplogic
author_rank: Frequent Contributor
timestamp: October 01, 2021, 11:39:49 pm
content: Quote from: Jeroen3 on September 24, 2021, 06:48:08 pmIn general I don't second guess the compiler but if there's a specific need for it use the compile specific directives and use the preprocessor to deal with it:#ifdef __GNUC__static inline ureg load32(void __iomem *address) __attribute__((always_inline))#endif

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 02, 2021, 12:34:43 am
content: Quote from: blacksheeplogic on October 01, 2021, 11:39:49 pmIf you want to *force* inlining with GCC, yes, this is the only thing that will work 100% of the time, even at -O0 optimization level.(Note: a minor remark here, but GCC yields an error when you define the attribute to a function at the end of the declaration, as you did. You need to put it at the beginning, for some reason.Code:error: attributes should be specified before the declarator in a function definitionI'm not 100% sure, but LLVM/Clang should support this attribute too. Just guessing, as it tends to support a good range of GCC extensions and attributes.But otherwise, unless you selectively disable function inlining through options, GCC will inline almost every function it can starting at -O1, with or without 'inline', even with or without 'static'. In the latter case, a non-static function will usually be inlined where it's called inside the same compilation unit (source file if you will), AND callable code for this function will also be generated at the same time, which will be the version that gets called outside of its compilation unit. Depending on optimization level though, GCC will inline more or less aggressively. At -O1, I think it will automatically inline only functions that are "small enough", but at -O3, it will inline practically ALL functions, which can lead to significantly larger binaries. I don't know GCC's exact criterions though, and this may change at every new version...So while "inline" is indeed pretty much ignored by most modern compilers *for call optimization*, the standard, as I quoted, still states that it can be an implementation-defined suggestion to the compiler that the call should be "fast" - without mentioning a particular method. The C standard add two footnotes about it:QuoteQuoteAs it's entirely up to the implementation, of course, it may do nothing as far as optimization is concerned. Which is indeed the case with modern GCC and many other C compilers.But yes, it's true that a number of C compilers already implemented "inline" well before it got standardized (and its getting included in the std probably comes from this fact, actually), and AFAIR, in older compilers, the keyword was indeed the only way of having the compiler inline a function (when the function could be inlined...), as they would not inline anything on their own.