• ip-guru (unregistered)

    Frost (like frist but cooler)

  • some guy (unregistered)

    I have seen worse #defines.

  • Anon (unregistered)

    Use extra parentheses

    #define GLYPH_DEFS
    X(glyph0, ({'0', 0x0E, 0x11, 0x0E, 0}))

  • (nodebb) in reply to Anon

    Use extra parentheses

    Only works if the extra parentheses are accepted in the place where the macro is invoked.

  • Tim (unregistered)

    TRWTF (apart from C using a macro pre-processor of course) is languages which don't allow trailing commas in lists. Not only does this cause enormous complications for anything which has to generate a list automatically (as in this case), it also causes spurious diffs in source code control when manually edited code is diffed or merged.

  • i be guest (unregistered) in reply to ip-guru

    This takes the cake. With a cherry on top. Also frosting, of course.

  • RLB (unregistered)

    C/C++

    Slap

    No. Bad Remy.

  • DanK (unregistered)

    I write C and Assembly code for a living, and I am okay with this, but on a code review I would ask you to explain the hack in a comment instead of writing "sorry". That sorry is not going to save the next person from being confused, but a proper explanation will.

    I would also consider using Python to generate the lookup table. TRWTF is not the C preprocessor. It's the people who keep trying to make it do things it was never intended to do.

    Also, please stop writing "C/C++". These are two very different languages. I am very annoyed by C++ developers who think that their best practices also apply to C. They do not.

  • oni (unregistered) in reply to Tim

    To be fair, C99 and C++11 explicitly allow trailing commas in initializer lists, and certain compilers (i.e. gcc) have supported them long before. Additionally, gcc has special handling when the token paste operator ## is placed after a comma, removing it when the following variable resolves to empty. In C++2a, there is also a new preprocessor construct that deals with trailing commas, at least when using variadic macros: VA_OPT

  • Anonymous') OR 1=1; DROP TABLE wtf; -- (unregistered)

    You can use variadic macros for a slightly less terrible hack, If you're compiling in C99 or later or C++11 or later (or a compiler that supports variadic macros as an extension):

    #define UNWRAP(...) VA_ARGS #define GLYPH_DEFS X(glyph0, UNWRAP({'0', 0x0E, 0x11, 0x0E, 0}))

    The parentheses for the argument to the UNWRAP macro solve the parsing problem, and the expansion of UNWRAP removes those parentheses.

  • Sole Purpose Of Visit (unregistered)

    Why not just use inline functions (in c++)? Or rely on compiler optimisation (in either language)?

    There may be a case for macro implementation of what is essentially a list or a dictionary these days, but frankly I'd want a very convincing performance test before I let this past a code review. And I've been doing C/C++ for thirty years, so this isn't language bigotry.

  • Naomi (unregistered) in reply to Anonymous') OR 1=1; DROP TABLE wtf; --

    Four spaces at the start of each line creates a code block!

    #define UNWRAP(...) __VA_ARGS__
    #define GLYPH_DEFS X(glyph0, UNWRAP({'0', 0x0E, 0x11, 0x0E, 0}))
    
  • Naomi (unregistered)

    (I'm sorry; I hit enter earlier than I intended to!)

    If your compiler does support __VA_ARGS__, you can also do it like this:

    #define GLYPH_DEFS \
    	X(glyph0, { '0', 0x0E, 0x11, 0x0E, 0 } ) \
    	X(glyph1, { '1', 0x09, 0x1F, 0x01, 0 }) \
    	X(glyph2, { '2', 0x13, 0x15, 0x09, 0 }) \
    	X(glyph3, { '3', 0x15, 0x15, 0x0A, 0 }) \
    	X(glyph4, { '4', 0x18, 0x04, 0x1F, 0 }) \
    	X(glyph5, { '5', 0x1D, 0x15, 0x12, 0 }) \
    	X(glyph6, { '6', 0x0E, 0x15, 0x03, 0 }) \
    	X(glyph7, { '7', 0x10, 0x13, 0x0C, 0 }) \
    	X(glyph8, { '8', 0x0A, 0x15, 0x0A, 0 }) \
    	X(glyph9, { '9', 0x08, 0x14, 0x0F, 0 }) \
    	X(glyphA, { 'A', 0x0F, 0x14, 0x0F, 0 }) \
    	X(glyphB, { 'B', 0x1F, 0x15, 0x0A, 0 }) \
    	X(glyphC, { 'C', 0x0E, 0x11, 0x11, 0 }) \
    	X(glyphD, { 'D', 0x1F, 0x11, 0x0E, 0 }) \
    	X(glyphE, { 'E', 0x1F, 0x15, 0x15, 0 }) \
    	X(glyphF, { 'F', 0x1F, 0x14, 0x14, 0 }) \
        /* ... */
    
    #define X(name, ...) const uint8_t name [] = __VA_ARGS__ ;
    GLYPH_DEFS
    #undef X
    
    #define X(name, ...) name,
    const uint8_t *const glyphs[] = { GLYPH_DEFS nullptr };
    #undef X
    
  • Nathan (unregistered)

    Haha! I ran into this exact problem yesterday! Squigly braces and all. Previously I've used the _ trick, but now I have VA_ARGS available :)

  • DanK (unregistered) in reply to Sole Purpose Of Visit

    You cannot call functions in a static initializer, so using functions to build the table statically is not an option. If you are using functions to build the table at run-time, you cannot expect the compiler to convert that to static initialization. It's not only a difficult problem, but there are very few cases where this would be a legal optimization. This code likely runs on a microcontroller where a const array would be stored in flash and used from there directly, but a non-const array would consume RAM. Most microcontrollers have significantly more flash than RAM. A function with a large switch statement could work, but now you are validating different code for each case instead of validating a function to look up an entry in an array. If you actually care about testing and code coverage, this is a pretty big difference. (Though I would not be surprised if this approach actually had better performance than using the table. Depends on the processor and the optimizations used by the compiler of course.)

    BTW, C also has inline functions.

  • No-one in particular (unregistered) in reply to DanK

    You cannot call functions in a static initializer

    Uncertain whether you’re talking about C or C++. If C++, then you can (albeit at the cost of dynamic initialization), and more to the point, you can even without the cost of dynamic initialization if the functions are constexpr. It’s super-convenient: make a constexpr function that generates the data you need, make a static-duration variable initialized to a call to that function, compile, and boom, the function runs at compile time and an image of the data lives in .rodata (aka Flash, if you’re doing embedded).

  • Blarg (unregistered) in reply to DanK

    Never heard of constexpr functions, then?

    https://en.cppreference.com/w/cpp/language/constexpr

  • DanK (unregistered)

    In case the last 3 comments, which were held for moderation, say something about what C++ allows you to do: my comment was about C, not C++. I should have specified that. C requires that initializers for objects with static storage duration (like this example) contain only constant expressions or string literals. I am aware that C++ is more flexible on this, but that's just another reason why talking about these two languages like they are the same thing is a bad idea. Also, even though C++ allows the compiler to evaluate those functions at compile time (assuming that some requirements are met), there is no guarantee that compilers will do so.

  • I'm not a robot (unregistered) in reply to DanK
    I would also consider using Python to generate the lookup table. TRWTF is not the C preprocessor. It's the people who keep trying to make it do things it was never intended to do.
    I rather like the idea of configuring the build system to run C source files through m4 ( https://en.wikipedia.org/wiki/M4_(computer_language) ) before compilation.

    For obvious reasons, the combination shall be known as "C-4".

  • sizer99 (google)

    I do this a lot - we have a large table of 'registers' in our firmware (in C) which can be set via the GUI (on PC) or serial port and many are backed to NVM. For instance, hot oven temperature is register 23, REG_HOT_TEMP. I have one file (reg_table.h) which has one line with all the parameters for each register - reg number, type, name, default value, RAM only or stored in NVM, value limits, etc... Then it gets included 3 separate times with different #defines as shown. Once to generate the enums for the names (REG_HOT_TEMP), once to make a const array in flash (so not eating RAM) for the constant parts of each register like type and limits, then once to make an array in RAM for the volatile bits like current value.

    Furthermore, the C# GUI (on PC) also parses reg_table.h so it knows what the registers are when it's talking to the device.

    Some people are initially horrified by the idea of including a .h file 3 (or 4) times with different invocations, but there really is no better way with .c so have so much information in several different places and keep it synchronized (the real problem). Imagine trying to keep 3 separate array/enum definitions consistent - it won't happen. This way it's self synchronizing.

  • (nodebb) in reply to sizer99

    The only real alternative to that is to have a step in your build rules that converts the definitive original form of the information into derived forms that you can use in each of the locations. Sometimes this is easier.

  • WTFGuy (unregistered)

    @Naomi ref

    Four spaces at the start of each line creates a code block!' Perhaps you already know this, but for sure some other folks don't.

    IMO a better way to do code blocks is to place 3 backticks on a line by themselves. Then the same again on the line below. Then write or paste whatever block of text you want treated as code between the two. with this technique if you're working with any code that's already got indention, you don't need to fussily inject 4 extra spaces into each line.

    The example below has two lines of code bracketed by two lines of triple backtick.

    #define UNWRAP(...) __VA_ARGS__
    #define GLYPH_DEFS X(glyph0, UNWRAP({'0', 0x0E, 0x11, 0x0E, 0}))
    

    And now we're back to regular text on the next line below the backticks.

  • WTFGuy (unregistered)

    Gaah!! I got the example right but screwed up the block quote. That second sentence is mine, not Naomi's. Apologies to all.

    No edit plus no preview = foolishness & frustration.

  • I dunno LOL ¯\(°_o)/¯ (unregistered)

    Definitely stunt coding. But if you're talking about readable and maintainable code for bitmap fonts, I prefer to abuse macros in a different direction. Let's see if I can do this without Markdown turning it into a mess.

    ... a bunch of defines, probably best in a .h file, a 4-bit set is useful along with it too ...
    #define ____XXX_ 0x0E
    #define ____XXXX 0x0F
    #define ___X____ 0x10
    #define ___X___X 0x11
    ... etc, now a big array for the font, you're a big boy you can use a multiply to scale it ...
    static const char tiny_font[] = {
    // glyph '0'
        ____XXX_,
        ___X___X,
        ____XXX_,
    // glyph '1'
        ____X__X,
        ___XXXXX,
        _______X,
    // glyph '2'
        ___X__XX,
        ___X_X_X,
        ____X__X,
    // glyph '3'
        ___X_X_X,
        ___X_X_X,
        ____X_X_,
    // glyph '4'
        ___XX___,
        _____X__,
        ___XXXXX,
    ...
    };
    

    That's a bit more readable than a bunch of random hex constants. If you want to make indexing it easier, I you could add something like this and use strchr to index it.

    static const char tiny_font_index[] = "0123456789ABCDEF";
    
  • mitch (unregistered)

    You can worry about code maintainability later.

    Sorry, do you even read this site?

    (I am intrigued how this will look as there seems to be no help or any indication how comment formatting works and which markup is used)

  • Just Me (unregistered)

    Wouldn't the line comment in the #define ruin everything?

  • (nodebb) in reply to dkf

    Generating the code from a config seems indeed the preferable way, but then again the macro stunts are essentially just that. Give it proper documentation and it is fine I'd say.

    Still potentially confusing, especially for code intelligence features, but so would be pretty much any solution. Though in principle the build process could place the preprocessed files, where the editor can find them, and add a header that says "generated file, don't edit directly".

  • DanK (unregistered) in reply to Just Me

    No, the comments are stripped before macros are expanded. I am not sure when this was first defined, but C99 defines this in the "Translation Phases" section.

    However, if this was a multi-line macro using \, that comment would cause issues. Line splicing happens before comments are stripped, so the rest of the multi-line macro would be part of the comment.

Leave a comment on “Underscoring the Comma”

Log In or post as a guest

Replying to comment #:

« Return to Article