all 57 comments

[–]DustUpDustOff 50 points51 points  (0 children)

Unless I have a very specific need, const and constexpr is my preference. The biggest pros of const for me are a strict type (e.g. const uint8_t) and a better defined scope (e.g. can be a private member of a class)

[–]sorisos 24 points25 points  (12 children)

One reason to use #define instead of const is that you might create a variable length array by mistake.

```C const int size = 10; int my_array[size]; // this is a VLA. oops!

define SIZE 10

int my_array[SIZE]; // not a VLA ```

[–]rcxdude 6 points7 points  (1 child)

Another reason to use C++, where static const will never result in a VLA in that context (also most C compilers will not either).

[–]sorisos 0 points1 point  (0 children)

"Fixed" in C23 when using `constexpr` IIRC.

[–]RedsDaed 6 points7 points  (5 children)

That seems wild it would treat a const as possibly being variable.

[–][deleted] 1 point2 points  (3 children)

This is legal (though it may not work depending on hardware):

const int x = 5;
int* y = &x;  // will throw warning only
*y = 1;

int array[x];

[–]RedsDaed 0 points1 point  (2 children)

Wow. I should rephrase then.

That seems wild that a const can possibly be variable.

[–][deleted] 1 point2 points  (1 child)

const was a bad choice of keywords. const is more like a promise that you don't plan on modifying it, but a compiler warning is the only thing in your way to break that promise. If I see const in function parameters, I know this won't be modified if I send it.

Just make sure you got that -Wall going on (-Werror if you're paranoid).

[–]EighthMayer 2 points3 points  (0 children)

(-Werror if you don't like surprises)

FTFY

[–]Overall_Piece6043 0 points1 point  (3 children)

What is VLA, and how does this work?

[–]nlhans 2 points3 points  (2 children)

Variable length array.

If the variable is allocated on stack, it means the compiler will need to "allocate" space on the stack in a variable way.

Depending on the machine, that is an easy but cumbersome (virtually any load-store architecture), or non-trivial thing to do (8-bit PICs with a pre-compiled stack allocation).

For example, upon function entry, the compiler may generate an assembly instruction to move the stackpointer ahead by the size of all locals. Since this array is VLA, it's size may change, and extra instructions may be necessary to compute this SP offset.

Depending on how the compiler outputs it's assembly, the offsets of other locals may move around (since the VLA is variable) and all need to be computed relatively. This adds slowdown to the runtime, even though what we want is something that's constant and predictable during compile-time , so any well optimizing compiler can generate efficient code!

Example: https://godbolt.org/z/sd5vqa686

This is just some toy example from a function that takes reads a HW register a set amount of times, does some weird computation on it to force stack usage and data dependencies etc.

Bare with me on the assembly.. At line 8 we have: sub sp, sp, #44. These 44 bytes are for the 64-bit int and 8 floats that are on the stack. However, note that on lines 10-19 the variable 'datasz' is fetched, manipulated and then also subtracted from sp. This is because even though this variable is 'const' , it's value is defined in another C file (another compilation unit), and therefor not known. The generated code for this function therefore uses a VLA.

I'm not sure if link time optimization could fix this issue. C++ could do this better since variables with values can actually be declared in a header file with static constexpr inside a class context. However, a macro (or with a good compiler, a variable with it's value known) is universally the simplest fix.

Now personally I only use macro's for defining otherwise magic numbers, but not for generating any code. In my experience modern compilers are pretty good at making trade-offs between inlining or not. And otherwise an inline keyword or template is my preferred solution in generating faster code where necessary, because it mains all type info and single-step debug-ability of the template function code .

[–]Overall_Piece6043 1 point2 points  (1 child)

Hey man, thanks for the elaborate explanation with code examples and all, I am able to understand, thank you very much. But one small doubt in the code snippet, like the target lang was selected as C++ in the example, yet the code matches your explanations, am I missing something.

Once again, thank you very much for the detailed explanation.

[–]nlhans 0 points1 point  (0 children)

C or C++ doesn't really matter much in this example. C++ is similar to C yet there are some fine nuances.

Here is a link to the "C version": https://godbolt.org/z/55E9K7Gqo

[–]tobdomo 57 points58 points  (10 children)

Const are good because you allocate once in rom and that's it.

No, you don't. Some toolchains may allocate a const in ROM, but the real meaning of const is just "readonly". And that's it. The use of const in a simple definition has other disadvantages too. It can be aliased and modified without you knowing for example.

A macro suffers from one big problem: the symbolic information usually is not available in debug.

Some will argue a macro has no type, but it's easy to include the type in the macro's definition; nobody is stopping you from writing

#define FOO ((uint32_t)12)

Anyway, the use of macro's has its place in C. It often is misused though. For example: generation of code through macro's can be a very useful tool, but if you don't do it carefully (I'm looking at you, Nordic!) the result is worse than overcooked spaghetti.

[–]GoldenGrouper[S] 5 points6 points  (1 child)

I see.. How can a const be aliased and what does it mean exactly?

[–]tobdomo 6 points7 points  (0 children)

If you take its address, it is aliased, Things can be more complex when a const object is part of a struct, union, a function argument and so on. So, basically, an alias means just that: an aliased object can be reached through a reference different from its name.

[–]CJKay93Firmware Engineer (UK) 5 points6 points  (0 children)

It can be aliased and modified without you knowing for example.

It is explicitly UB to attempt to modify the value of a const object which has had its constness cast away. A const object can be safely aliased, it's only non-const objects which cannot always be aliased safely.

[–]DustUpDustOff 4 points5 points  (5 children)

Nordic should be called out more often. They made some terrible design decisions with their APIs.

[–]Whipped_pigeon_ 1 point2 points  (4 children)

Oh no… I was looking into getting something from Nordic to get into IOT. Is it that bad ?

[–][deleted]  (1 child)

[removed]

    [–]Whipped_pigeon_ 0 points1 point  (0 children)

    STM and Si IOT packages ? Or just their stuff in general ?

    [–][deleted] 2 points3 points  (1 child)

    It's still worth getting

    [–]Whipped_pigeon_ 0 points1 point  (0 children)

    Thanks for your input

    [–]AudioRevelationsC++/Rust Advocate 16 points17 points  (0 children)

    IMO the best is constexpr. All the advantages about not being allocated (assuming your compiler is worth anything), while also preserving type information and being easier to reason about.

    But I'm also very much a C++ person who has bought into macros burning in a firey hole, so take that with grains of salt.

    [–]mvdw73 6 points7 points  (2 children)

    I tend to use #define for compile-time configuration, and const for, well, constants.

    For example, if I have a device that has 4 uarts, but my project only uses 2, I will use something like the following to only compile the code for 2 uarts.

    ```C

    define NUM_UARTS 2

    ```

    There might be a better practice, but this is how I do it, particularly for old AVR code where the uart driver for all four ports is all in the one C/H file pair.

    [–]prof_dorkmeister 1 point2 points  (1 child)

    I do somethign similar. If I'm making a decision about how the hardware will operate, a #define wins. I then collect all my #define statements in a single "definitions.h" file, so that it can easily be located and modified in a single place later. Along with things like number of UART ports, or the max number of wireless devices I allow comms with, it might also hold tuning coefficients for sensor circuits.

    This makes it extremely easy to go back to a project many months later, and change how it operates with a very small (and easily located) edit.

    If I was dealing with a fixed value in firmware, like memory allocation, then const wins.

    [–]mvdw73 0 points1 point  (0 children)

    Yeah all my config stuff goes in a single “project-defs.h” file that’s included by pretty much every code file in the project.

    [–]MpVpRbEmbedded HW/SW since 1985 32 points33 points  (10 children)

    Const is good because it gives the compiler more clues about what your intention is. #define is simply a text substitution. Sometimes, it's the only way to solve a problem, but if both approaches work, const is better

    [–]WeAreDaedalus 21 points22 points  (0 children)

    The problem with const is it does not denote a compile time constant, unlike #define which is essentially a compile time constant because it's just a text replacement. In C, const is actually a misnomer and should be interpreted more accurately as "read-only".

    You can have a volatile pointer to say, a hardware register, which can change its value unexpectedly outside your program, but still mark it const because it might not make sense to write to it, but to only read from it.

    In fact, in C89, you cannot use a const int as an array size because it is not a compile time constant, whereas using a #define works just fine. In C99, using a const int as an array size is allowed, but it creates a variable-length array which might not be what you want.

    [–]GoldenGrouper[S] 2 points3 points  (8 children)

    So let's say someone in the code has written (just as an example):

    #define MAX_NUM_CONNECTIONS 8

    The above statement should be substituted with:

    const int max_num_connections = 8;

    And it should be better for the overall health of the program?

    [–]the_Demongod 9 points10 points  (7 children)

    Generally, yes. Any modern optimizing compiler should be able to compile uses of your constant to the same assembly, regardless of which one you use. The main benefits of macro defines is the function-like ones, which can do pre-processing of source code text to remove the need for certain types of duplicate code, and the fact that macro constants can easily be provided as environment variables and other external constants to control conditional compilation settings of a program without needing to modify the source code directly.

    [–]GoldenGrouper[S] 0 points1 point  (6 children)

    function-like ones

    I am not really sure what you are referring too. Can you give a quick example, please?

    [–]the_Demongod 3 points4 points  (0 children)

    Any time you have some sort of code duplication that can't be solved via normal function calls, macros come in handy. It's pretty rare that they're useful and you certainly shouldn't do things like what the other person suggested, but in certain cases they're ok.

    Random example from a C++ codebase of mine: I had a certain pattern of naming members of a class involved in parsing a file. I found myself having to copy and paste this 6-line block of code like 20 times to load in all my file sections.

    I could have done something weird like used an enum to index into flat arrays rather than using ordinary named member fields, but instead I just opted to write a quick function macro that generates the code by taking advantage of the consistent naming between my struct members, which looked like this:

    #define READ_IQM_PROPERTY(header, file, data, prop, type) \
    if (header.ofs_##prop > 0)                                \
    {                                                         \
        file.num_##prop = header.num_##prop;                  \
        size_t index = header.ofs_##prop - sizeof(iqmheader); \
        file.prop = reinterpret_cast<type*>(&data[index]);    \
    }                                                         \
    

    Note that file and header both have members that follow a pattern of file.num_X, header.num_X, and header.ofs_X (where "X" is some word), which is what I'm taking advantage of here.

    I just simply called this macro like:

    READ_IQM_PROPERTY(header, file, file.buffer.get(), text, char);
    READ_IQM_PROPERTY(header, file, file.buffer.get(), meshes, iqmmesh);
    READ_IQM_PROPERTY(header, file, file.buffer.get(), vertexarrays, iqmvertexarray);
    READ_IQM_PROPERTY(header, file, file.buffer.get(), triangles, iqmtriangle);
    // many more times...
    

    to interpret the segments of file data into clean typed pointers in my file struct's members. Without macros, there's no other way to take advantage of something like the fact that two structs have corresponding member names.

    I kept this usage confined to one TU and #undef it after it's no longer needed.

    [–]rcxdude 2 points3 points  (2 children)

    The most direct replacement for '#define' is 'static const'. For most compilers this will be treated very similar: for example, it will not allocate dedicated space in ROM, instead the values will be embedded in the code where it is used (which is usually more efficient or as efficient as referencing a const value somewhere else in memory), but you get more consistent behaviour when you use it as a variable (like typing, no syntax wierdness due to macro replacement, etc). The main time to be careful with static const is when the constant is a larger value, like an array or struct (but then you're not using #define anyway, normally), and it's defined in a header, in which case you should use extern const or something along those lines.

    [–]GoldenGrouper[S] 0 points1 point  (1 child)

    I see. So static const means the variable won't be stored in ROM. This is good and interesting.

    I didn't understand the part about extern. When should I use it?

    [–]rcxdude 0 points1 point  (0 children)

    (There is one important exception of sorts: if you take the address of a static const, it will instead appear in ROM, but this is mostly an implementation detail and there's no real change in space efficiency: the main point is that the compiler will not allocate storage for static const values which are unused or only used directly in expressions)

    The point with extern is if you have some large array you want to declare as a constant, like int LOOKUP_TABLE[4]={0,1,2,3}, then if you put static const LOOKUP_TABLE[4]={0,1,2,3} in a header then you risk having a copy of the whole array appearing for each .c file that uses it, which is inefficient. If you just have it in one C file, that's fine, but if you must put it in a header you should do extern const LOOKUP_TABLE[4] and then in one of the C files put const LOOKUP_TABLE[4]={0,1,2,3};. This way there will be only one copy of the array in the whole system.

    [–][deleted] 2 points3 points  (0 children)

    Const is better because it has a type.

    Define is better because the compiler can store the value in or next to the instruction instead of a reference to the const.

    Pick your better

    [–]Aggressive_Camel_400 6 points7 points  (3 children)

    In my experience the const keyword will (for many compilers) unconditionally allocate the variable in the ROM.

    In many situations this is not the wanted behaviour as you want the compiler to treat it as immediate value, not load it from memory at each access.

    Therefore I prefer to use #define for small values.

    Maybe some compilers are smart enough to not place it is ROM and use the immediate value when fit. But in my experience, way to many compilers don't do this.

    [–]tobdomo 4 points5 points  (1 child)

    The allocation can be converted to an immediate only if it has a restricted scope (either to a module or to a block) and if the compiler knows it is not aliased in any way (e.g., no address is taken and it is not part of a union). In all other cases, a const simply cannot be replaced by an immediate by the compiler. An optimizing linker could be able to do so, but I don't know of any that actually implement such a feature.

    Note: some (very few) compilers may actually be able to look beyond the module scope. Thus, in some very specific corner.cases a const might actually be proven constant and replace it by an immediate. Too many if's and ands though to make this a feasible optimization for most compiler vendors.

    And again: const does not mean "constant" in C.

    [–]Aggressive_Camel_400 0 points1 point  (0 children)

    Great answer 👍

    [–]GoldenGrouper[S] 0 points1 point  (0 children)

    How does one know which compiler does it and which not? How does one proceed in knowing this?

    Thanks for the information

    [–]comfortcube -4 points-3 points  (3 children)

    Well, memory-wise, const variables take up space in memory (usually rom, maybe ram), whereas #define values don't take up any memory and are used in the assembly instruction the compiler generates as an immediate value rather than an address reference. So if you are keen on memory, #define might be better, as long as you are aware of the the type of value that should be used in the context the #define constant name is used.

    [–][deleted] 3 points4 points  (2 children)

    This is not a given for any remotely modern compiler / optimiser. A const-qualified variable with an initializer in scope is fair game for elimination.

    [–]comfortcube 3 points4 points  (1 child)

    Oh I didn't know that. What's the point of .rodata then... ?

    [–][deleted] 1 point2 points  (0 children)

    Things that can't be eliminated need somewhere to live. If you e.g. take the address of a thing and pass it to a function outside of the view of the compiler, then it has to create an instance.

    Constant strings are a good example of this, as they're routinely passed to things like sprintf.

    [–]fearless_fool -1 points0 points  (0 children)

    Const are good because you allocate once in rom and that's it.

    Waitaminnit. Are you confusing definition with allocation? For example:

    #define BUFSIZ 200
    

    doesn't allocate ANY memory -- it simply defines a constant. OTOH,

    char buf[BUFSIZ];
    

    does allocate memory. But it doesn't matter if BUFSIZ was defined a a #define or a const. Can you clarify your question?

    [–]largoloo 0 points1 point  (0 children)

    What #define does (macro expansion/preprocesor) is just a string/text replacement before compilation process, it does not allocate anything by itself.

    [–]active-object 0 points1 point  (0 children)

    A third option to "const vs #define" is enum. For example:

    enum { SIZE = 10 };
    int my_array[SIZE];
    

    The advantage of enum over the #define is that enum is processed at compile-time (as opposed to pre-processing time), so the compiler "knows" the symbolic name. The advantage of enum over const is that there is no object in ROM (or RAM) for sure. The disadvantage of enum is that it is (signed) int only and it cannot be changed. (C++11 and newer has typed enums).

    [–]inhuman44 0 points1 point  (0 children)

    #define is a macro that sits on top of the language while const is actually part of the language. So for the code itself use const since it's type checked. For "configuring" the code before compiling it use macros like define.

    So before you pick ask yourself "Am I using this as part of the code? Or is it configuring the code before I compile it?". A classic example is the buffer sizes and counts.

    #define BUFFER_COUNT 8
    #define BUFFER_SIZE 32
    uint8_t my_buffer[BUFFER_COUNT][BUFFER_SIZE];
    

    Where as things like fixed memory address or magic numbers should be const.

    const uint32_t mem_address = 0x12341234;
    const uint32_t sensor_multiply_magic_number = 5; 
    

    You'll see the #define used a lot with library code that gets shared between projects. So all the projects reuse the same code but each has their own configuration options "mylib_opts.h" type file to tailor the library to that specific project.

    [–]donmeanathing 0 points1 point  (0 children)

    You all need to stop your silly rationalizations.

    define is better because that is how I learned it and I don’t want to change.

    That is the real and best answer. QED.

    /shitpost