News for hackers. Stuff that matters.
Re:Dangerous Embedded C Coding Standard Rules (2011) by theseoafs on Monday December 30, 2013 @01:09PM (#6985876)
> If you need macros to hack C to enable some functionality not inherent to the language, you should change the approach or switch to a different language.

I think if you continue to do serious C hacking, you'll find a lot of places where macros are legitimately a good choice.

For example, I've found that macros are very, very useful when it comes to implementing error handling in a robust way. In C, the only real way to detect whether a function has failed is to return an error code. The caller then has to test for the error code and take the appropriate action if there was an error. I'm working on a side project now that was doing a LOT of this because it does a lot of IO and memory allocation (and basically any operation you do on a file or any call to `malloc` or a function that calls `malloc` can spontaneously fail in C). So my source code, after a while, ended up looking more or less like this:

    if ((f = open_file()) == NULL)
        return ERROR_CODE;
    if ((s = allocate_string()) == NULL)
        return ERROR_CODE;
    if (write_string_to_file(s, f) < 0)
        return ERROR_CODE;
... and so on, and so forth. It got to the point where basically all of the function calls I did needed to be manually checked for an error, which made all of my code really messy and hard to read. The only real way to refactor these checks in pure C without sacrificing robustness is to use a macro:

    #define MY_ASSERT(c) do { if (!(c)) return ERROR_CODE; } while (0)
    MY_ASSERT((f = open_file()) != NULL);
    MY_ASSERT((s = allocate_string()) != NULL);
    MY_ASSERT(write_string_to_file(s, f) >= 0);
This has the same effect as the code above, without sacrificing comprehensibility or readability.

> Also macros are usually used to "speed up" the code. You should never optimize before finding the real bottleneck in your code. And thus always use functions instead of macros.

I think a lot of people on HN and /r/programming fail to understand why premature optimization is a bad thing. A lot of programmers hide behind the "premature optimization is the root of all evil" thing to justify their pre-existing biases or their inefficient code. The point is that you shouldn't spend a great deal of time optimizing functions or algorithms unless optimizing those functions or algorithms will be productive. It's wasteful to spend a bunch of time optimizing your IO operations, for example, if 95% of the time is being spent in the database. The macro-vs-function debate has nothing to do with that. Writing a macro definition takes no more effort than writing a function definition, so you shouldn't be opposed to writing

    #define SOME_OPERATION(x) (((x) * 100) / 3 + 500)
rather than

    int some_operation(int x) { return (x * 100) / 3 + 500; } 
That isn't premature optimization, you're just guaranteeing that the compiler won't introduce the overhead of a function call.
Re:Dangerous Embedded C Coding Standard Rules (2011) by Someone on Monday December 30, 2013 @04:31PM (#6987105)
So my source code, after a while, ended up looking more or less like this:

    if ((f = open_file()) == NULL)
        return ERROR_CODE;
    if ((s = allocate_string()) == NULL)
        return ERROR_CODE;
    if (write_string_to_file(s, f) < 0)
        return ERROR_CODE;*
I hope for you that's more 'less' than 'more'. That code leaks resources whenever an error occurs. Can't allocate the string? Oops, kept the file open. Write failed? Oops, kept the file open, and leaked the result of allocate_string.

In C, this kind of error handling screams for the use of goto:

    int result = ERROR_CODE;
    FILE * f = NULL;
    char * s = NULL;
    if((f = open_file()) == NULL) goto ERR;
    if((s = allocate_string() == NULL) goto ERR;
    ...
    result = NOERROR;
  ERR:
    if( s != NULL) free_string( s);
    if( f != NULL) close_file( f);
    return result;
Given that you need that to write robust code, I don't think the macros make things much clearer.
Re:Dangerous Embedded C Coding Standard Rules (2011) by ZoFreX on Monday December 30, 2013 @06:32PM (#6987838)
An advantage of using goto to handle errors is that you don't need that condition checking in the teardown:

    int result = ERROR_CODE;
    FILE * f = NULL;
    char * s = NULL;
    if((f = open_file()) == NULL) goto end;
    if((s = allocate_string() == NULL) goto closefile;
    if((some_other_thing() == NULL) goto freestring;
    ...
    result = NOERROR;
  freestring:
    free_string( s);
  closefile:
    close_file( f);
    return result;
Re:Dangerous Embedded C Coding Standard Rules (2011) by kstenerud on Monday December 30, 2013 @10:28PM (#6988762)
That's fine until you have multiple lines that goto closefile or freestring, and you mix them up at some point, or you try to refactor the code and forget to update one of your gotos. The resulting bug will wait beneath the waters until one day you get a mysterious "out of file descriptors" or "out of memory" error at some random place in your program after it's been running for awhile.

And really, the extra condition check likely isn't going to affect the critical path, but it will make it a lot easier to reason about the code, especially when someone else has to modify it.

Re:Dangerous Embedded C Coding Standard Rules (2011) by ZoFreX on Thursday January 02, 2014 @06:49AM (#6999183)
It's really a matter of both taste and situation. I've seen some very compelling examples from the Linux kernel where this is definitively easier to follow than the alternatives (Linus has made some good defences of goto for this purpose), but I've never felt it was the clearest option in any code I've written myself.
Re:Dangerous Embedded C Coding Standard Rules (2011) by femngi on Monday December 30, 2013 @01:36PM (#6986033)
I have to strongly disagree with your example. In fact I feel it may just be one of the worst usages of macros I have seen in that you are hiding the control flow of the program and thus obfuscating your code for other programmers. It might be easier for you to read because you wrote it but for every one that comes after you it will be a massive pain that adds unneeded additional cognitive overhead.

I say this from the point of view of someone having to debug your code: "OK so step, step, step, run to this line... wait, what? How did this function return when there is no return statement? Let me run that again from the beginning... ... ... What? This ASSERT macro doesn't actually assert it just returns?"

Keyboard out window etc.

Please stick to language conventions. Macros are bad because they hide what the code is actually doing.

Re:Dangerous Embedded C Coding Standard Rules (2011) by theseoafs on Monday December 30, 2013 @02:34PM (#6986359)
To be fair, I don't see why you would have to throw your keyboard out the window. If I were debugging a source file and saw the same ASSERT macro on every other line, I would break out `grep` and track down the (extremely simple) definition so I could understand what it's doing. Moreover, returning with an error code is IMO entirely sane behavior for a macro with ASSERT in the name, especially considering halting execution of the program altogether generally isn't a good thing if you're writing a C library.
Re:Dangerous Embedded C Coding Standard Rules (2011) by deletes on Monday December 30, 2013 @02:51PM (#6986470)
The point of assert is to check your code has values within your required parameters, so you the code is easier to debug while coding. Asserts should never be left in your code compiled for release. Even c library assert is aware of that, as it has the NDEBUG macro, which removes every assert in the code. That is also the reason why you should Never put code that might mutate your variables in them.( function calls or assigning, for example)

http://www.cplusplus.com/reference/cassert/assert/

Quote from the link: Therefore, this macro is designed to capture programming errors, not user or run-time errors, since it is generally disabled after a program exits its debugging phase.

Of course you can name your macro whatever you want, even assert, but it will confuse other developers used to c library functions.

Re:Dangerous Embedded C Coding Standard Rules (2011) by theseoafs on Monday December 30, 2013 @02:54PM (#6986494)
Fair enough -- assert was just the first name that came to mind. Perhaps something more descriptive like RETURN_ERROR_IF_FALSE would have been a better name.
Re:Dangerous Embedded C Coding Standard Rules (2011) by drivers99 on Monday December 30, 2013 @01:36PM (#6986035)
The first block of code seems more readable to me than the second. I'm used to seeing the first; it's idiomatic C. I don't understand why it says "do ... while (0)" in the macro, for instance. Wouldn't you want to handle different errors in different ways anyway? In your example, you're just going to leave the file open and the string allocated? Isn't trying to influence how the compiler compiles it by using a macro instead of a function a clear example of (premature) optimization?
Re:Dangerous Embedded C Coding Standard Rules (2011) by theseoafs on Monday December 30, 2013 @02:19PM (#6986256)
> The first block of code seems more readable to me than the second. I'm used to seeing the first; it's idiomatic C.

The benefits of the macro (IMO) aren't obvious from the example I gave. Like I said, I'm currently working on a personal project where macros have been the saving grace. Attaching a conditional return to every individual statement was making my code extremely hard to maintain (even for myself, who wrote it) -- wrapping common error cases in a macro made everything a whole lot simpler, and allowed me to work on my algorithms at a high level without having to run my eyes over the same error-checking `if` statements a million times.

> I don't understand why it says "do ... while (0)" in the macro, for instance.

It's normal for many macros, so that you can use them like normal statements.

> In your example, you're just going to leave the file open and the string allocated?

I sort of just made that example up off the top of my head -- you're right about the memory leak.

Re:Dangerous Embedded C Coding Standard Rules (2011) by nitrogen on Monday December 30, 2013 @04:19PM (#6987036)
Re:Dangerous Embedded C Coding Standard Rules (2011) by kstenerud on Monday December 30, 2013 @10:42PM (#6988804)
"That isn't premature optimization, you're just guaranteeing that the compiler won't introduce the overhead of a function call."

That's precisely what premature optimization is: Reducing assumed overhead before you've even established where the bottleneck is, if any. By using a macro, you've traded away all of the benefits of a function. If you had demonstrable need to avoid the function call overhead, you could have declared the function inline static.

Most performance issues can be solved with algorithmic changes. This level of optimization should only be done as a last resort, and profiled extensively to ensure that you really are making a difference for the better. Many "low level" tricks that on the surface look like they'd speed things up can in fact have the opposite effect on modern processors. For example, it's possible that this macro optimization could blow up your cache if some_operation gets called a lot and you tried unrolling loops.

Re:Dangerous Embedded C Coding Standard Rules (2011) by deletes on Monday December 30, 2013 @02:17PM (#6986250)
That is almost exactly like some code I have written in a project and at that time it did it's job. After revisiting the project months later after I have completely forgotten the details, I got frustrated over the macro and what did it actually do( even though I have written it ). It took me some time to track down the macro definition and convert the code in my head to understand it again. But if the code would be simple C as in your first example I wouldn't waste time on it.
Re:Dangerous Embedded C Coding Standard Rules (2011) by theseoafs on Monday December 30, 2013 @02:25PM (#6986297)
You're right, the example is far too simple to warrant the introduction of a macro like this.
Re:Dangerous Embedded C Coding Standard Rules (2011) by angersock on Monday December 30, 2013 @04:09PM (#6986966)
"It took me some time to track down the macro definition "

Right-click -> Go to definition?

Your IDE must suck.

Re:Dangerous Embedded C Coding Standard Rules (2011) by deletes on Monday December 30, 2013 @05:17PM (#6987406)
What a great comment from a longtime member of HN. Not only did you make a wonderful example of name-calling and an ad-hominem argument in a single sentence, you also failed to dispute the central argument which was that time is lost while translating the macro.

Apart from your already ( apparently )wast knowledge of various development tools, please also consider improving your conversational skills.

Re:Dangerous Embedded C Coding Standard Rules (2011) by nitrogen on Monday December 30, 2013 @04:21PM (#6987048)
In Vim, Ctrl-W followed by i will try to open the definition of the identifier under the cursor in the files included by the current file (sometimes it only finds references or useless mentions inside of comments, though).
Re:Dangerous Embedded C Coding Standard Rules (2011) by anywherenotes on Monday December 30, 2013 @02:20PM (#6986262)
The code I used is more like this:

  if ((f = open_file()) == NULL)
  {
    RETURN_ERROR(OPEN_FILE_ERROR);
  }
  if ((s == allocate_string()) == NULL)
  {
    RETURN_ERROR(ALLOCATE_STRING_ERROR);
  }
  if ...
And RETURN_ERROR is defined as:

  #define RETURN_ERROR(my_error) { \
    if (f != NULL) { fclose(f); f = NULL; } \
    if (s != NULL) { free(s); s = NULL; } \
    ...
    return (my_error); \
  }
Re:Dangerous Embedded C Coding Standard Rules (2011) by nitrogen on Monday December 30, 2013 @04:24PM (#6987065)
The approach I use, and that used many places in the Linux kernel, is to use goto for error handling:

  if(failure_1) {
      goto error;
  }
  if(failure_2) {
      goto error;
  }
  
  // Do stuff
  return 0;
  
  error:
  if(f != NULL) {
      fclose(f);
  }
  if(s != NULL) {
      free(s);
  }
  return -1;