3

In C, we like to use #define or const globals to avoid hard-coding values and strings.

How can we avoid hard-coding strings that need to be formatted? Consider, for instance, building a filepath to an i2c bus in linux:

snprintf (dev, size, "/dev/i2c-%u", bus_no)

I don't like the idea of adding a #define I2C_BUS_FMT "/dev/i2c-%u" to the top of the file (or the header file), because when you read snprintf (dev, size, I2C_BUS_FMT, bus_no) you can't see how many and of what type the format specifiers are... you need to take it by faith that it accepts a single unsigned int or go check.

The same problem applies to const globals.

How can I avoid hard-coding format strings without moving important information away from the place where it is used?

  • 2
    This is much the same problem as with any constant. If you're using it once, you'll probably get away with a literal in situ, but if another of the same is introduced you won't know if they should be the same or whether they can be different. See here for a longer discussion of this. – Robbie Dee Jul 28 '16 at 15:03
  • 1
    @RobbieDee Even if you're using it only once, it's hard to know what's the context. For instance, if you had the constant USER_NOT_FOUND_ERROR_MESSAGE, you have no way to know that you need to provide an error code, the missing user name and the date when the error happened before displaying that message. I think this problem is very different from the one you linked, even though they're both quite important. – Vincent Savard Jul 28 '16 at 15:07
  • I find this ironic, in that more often having a non-hard-coded format string is viewed as a subtle but potentially very serious security problem. If the format string itself is supplied by the user, then they can easily cause buffer overrun / UB. But of course what you are debating is using a macro for the string constant or not, not whether or not the format string should be a constant. – Chris Beck Jul 28 '16 at 19:14
7

Don't feel obliged to define every constant as a preprocessor symbol just because someone calls it a best practice. There are good reasons for and against it, and the best way to decide is to look at the situation and use your judgment.

The question to ask yourself is "if I define this as a preprocessor symbol, where will it be used?" If the answer is "in one function," there's no reason it shouldn't be hard-wired in the one place where it's used. That keeps all of the knowledge of how it works concentrated in one place.

I'm going to stick my neck out and assume that in your program, format_i2c_bus() is the API for formatting those paths. This means anyone who wants it done should be calling that function to get the job done. If you define a symbol for the format string, you make it available for use in any other function that can see it. That leaves room for someone to do their own formatting, which is the last thing you want because you no longer have a single place to make adjustments when the formatting of the path changes.

There's an old guideline in user interface design that says if the user can't do something, it shouldn't be shown to them as an option. That applies to software, too: if other modules shouldn't be making use of something, make sure it's not visible to them.

  • okay, but format_i2c_bus() was just an example i cooked up for this question -- i didn't really mean to ask about the merits of defining constants versus hard-coded values. what if it wasn't a function, and there were similar snprintf statements in several places throughout the file? – Woodrow Barlow Jul 28 '16 at 16:22
  • @WoodrowBarlow: Then you need to consider whether those calls to snprintf() need to be consolidated into a function. Not having scattered copies of the same thing is exactly the reason why you define a function in the first place. – Blrfl Jul 28 '16 at 16:26
  • so, if i were to generalize your answer here, my understanding more-or-less is "instead of depending on constants, move string formatting logic into inline functions". is that correct? (i'm not questioning the validity, just want to be sure i'm understanding you.) – Woodrow Barlow Jul 28 '16 at 16:41
  • @WoodrowBarlow: Yes. The even more-general answer would be "if you're doing it in more than one place, it belongs in a function." – Blrfl Jul 28 '16 at 16:51
0

In this case, code could take advantage of string literal concatenation.

// Define this someplace else
#define I2C_BUS_PREFIX "/dev/i2c-"
...
// Keep specifier informaiton local
snprintf(dev, size, I2C_BUS_PREFIX "%u", bus_no);

Further, code could use this to right-size the buffer

//                                                  1/3 is about log10(2)
#define UNSIGNED_STR_SIZE (sizeof(unsigned)*CHAR_BIT/3 + 2)
...
#define DEV_SIZE     (sizeof(I2C_BUS_PREFIX) + UNSIGNED_STR_SIZE)
char dev[DEV_SIZE];
snprintf(dev, sizeof dev, I2C_BUS_PREFIX "%u", bus_no);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.