Ways to define C constants

Discussion in 'C Programming' started by James Harris, Jun 2, 2014.

  1. James Harris

    James Harris Guest

    I'm a bit confused over how 'best' to define constants - especially small
    integer constants - in C. There seem to be some options so I wondred if you
    guys would recommend one over the others.

    I'll use a 16-bit port number in each example. The ui16 in the last example
    is the typedef of a 16-bit unsigned int.

    First option, with #define.
    /* Define address port */
    #define PORT_A 0x70

    Second option, using enum
    enum {
    port_a = 0x70 /* Address port */

    Third option, using const
    static const ui16 port_a = 0x0070; /* Address port */

    Are they all valid and are there any other ways to define integer constants?

    I think I know the basics (#define is preprocessor, textual, works for more
    than just numbers, widely used, needs parens if an expression etc; enum is
    good for assigning consecutive integers, includes limited type info,
    possibly modifiable through a pointer(?); const not guaranteed to be safe as
    can be modified through a pointer but includes good type info).

    Assuming they are all valid, under what circumstances would you choose one
    over the others? If you had to use #define for some constants such as string
    literals would you use #define for the integer constants too for

    Would you ever define an enum tag and/or define a variable using the enum?

    I know the static keyword would keep the id private to the source file but
    would the const be better with or without static? Would that depend on
    whether the definition was in the .c source file or was in a header file
    that might be included in multiple object files?

    Finally, what would you recommend wrt capitalisation of the identifier name?
    As you can see, I've used both lower case and upper case in the different
    examples. Aside from the #define I'm not sure what's considered to be more

    Sorry that's a lot of questions!

    James Harris, Jun 2, 2014
    1. Advertisements

  2. The ideal method for something like a port is this.

    int main()
    /* use Ox74 for port A */
    intitialiseIO(0x74, "An argument", "Another setting");

    intitialiseIO(int porta, char *setting1, char *setting2)
    /* set port a to 0x74, then pass it down to all other IO */

    Since PORT_A is used in only one place, it hardly matters whether you
    #define it or hardcode it with a comment.

    For various reasons this isn't always possible, but it's good general
    Malcolm McLean, Jun 2, 2014
    1. Advertisements

  3. James Harris

    Kleuske Guest

    The 'static'-bit in the last option is out of place. Apart from that,
    the last option does not deliver a const expression, so you can't use it
    in (for instance) array-sizes or case labels. The other options are
    (almost) equivalent.

    See question 2.22 and 11.8.
    Depends on the situation.

    If I want some constants (and I don't really care what exactly they are,
    as long as they're different), I'd choose enums. If I want constants to
    be set at compile time (e.g. to set configuration stuff) I'd go for

    #ifndef SOMETHING
    #define SOMETHING "something"
    Probably. Again, the situation above (some constant number and I don't
    really care what it is) would be an exception.
    You can export the constant, which may be a advantage. Still, you can't
    use it as a case label or size-specifier for an array.
    Stick to the coding-style in vogue where you work and be consistent in
    using it. Any number of arguments can (and have) been made for almost any
    style you care to come up with. Arguing about it is usually not very
    productive. It's (mostly) a question of taste and 'de gustibus non
    disputandum est'.

    As long as it's not a viable entry to the obfuscated-C-contest,that is.
    Not to worry. That's why this group exists.
    Kleuske, Jun 2, 2014
  4. James Harris

    David Brown Guest

    All three methods will work.

    It is best to use "static const" rather than plain "const" in a case
    like this. In general, use "static" for file-level objects whenever it
    is possible. For a "static const int" definition like this, you can put
    it in a header or a C file - as long as your compiler has optimisation
    enabled, and you don't do anything odd like take its address, the
    compiler will generate as optimal code as if you had used the #define
    method, and you have the advantage of the type information.

    You cannot /legally/ modify a const "variable" through a pointer - it
    would be undefined behaviour. So the compiler can optimise with the
    assumption that the value never changes - and no one knows what will
    happen if you /do/ try to change it.

    Regarding capitalisation, it is a common but not universal custom to
    capitalise #define'd macros. (Personally, I rarely capitalise them -
    but I believe I am in the minority there.) Opinions are much more
    divided about enum constants and static const's. I don't capitalise
    them - but some people do. /You/ have to decide if the capitalisation
    improves the legibility of /your/ code.

    Don't use made-up types such as "ui16" - prefer to use the standard C99
    types from <stdint.h>, such as uint16_t. They are standardised and
    commonly used, and let you write more portable code.

    One other thing to consider here is that you are probably not very
    interested in the address of port A. What you are usually interested in
    is the contents of the register at that port. A common way to do that
    is something like this:

    #define portA (*((volatile uint8_t *) 0x0070))

    Then you can write things like "portA |= 0x01;" to turn on bit 1 of port A.
    David Brown, Jun 2, 2014
  5. James Harris

    BartC Guest

    It's a terrible general principle!

    Always name constants like this. Then you will know (if named properly) that
    this is the port address, something that is not at all certain from the
    above (since you talk about setting the port to 0x74).

    Also if there are any other 0x74 values floating around, nothing to do with
    ports, then they won't get mixed up. (And when a new version has the port on
    0x84, you won't inadvertently change something entirely unrelated.)
    BartC, Jun 2, 2014
  6. James Harris

    BartC Guest

    Not really. The 'const' method won't deliver a value that can be used in
    many situations where you want an actual compile-time expression. (Array
    bounds and case-labels have been mentioned. They will apparently work for
    arrays, but might quietly create a VLA instead. They also can't be used to
    construct other compile-time expressions.)

    The enum method only works for signed integers (otherwise is the best of the

    #define has all sorts of issues with scope (using it for a private constant
    inside a function, for example, is a bit of a pain). Also any expression
    used to define it, could be re-evaluated, with potentially different
    results, at each point of use.)
    Disagree. So long as 'ui16' is itself defined in terms of uint16_t, then I
    can't see the problem. It's not as though the name is completely obscure or
    BartC, Jun 2, 2014
  7. The constant is used once. From then on, it's a variable. Since it only appears once,
    it's not important whether it appears as a literal or not.
    The snag with the #define method is that it's easy to write code which relies
    on the #define being a certain value. Then when we change the definition, it
    breaks. If the number is a variable, that's still possible, but it's much less likely.

    Also, you can't use the same subroutine to write a byte to port 0x74 and 0x84,
    everything is tied to having a single port.
    Malcolm McLean, Jun 2, 2014
  8. James Harris

    James Kuyper Guest

    The term "integer constant" is defined by section It refers
    very specifically to things like 1, 2U, 3L, 04, and 0x5. None of the
    things you give below is an integer constant.
    This defines a macro that expands into an integer constant. For
    practical purposes, it's effectively the same as an integer constant,
    except that preprocessor directives can test whether or not it has been
    With this definition, port_a is an enumeration constant, rather than an
    integer constant. It does, however, qualify as an integer constant
    With this definition, port_a is an expression of integer type with a
    constant value. However, it does not qualify as either an integer
    constant or as an integer constant expression:

    "An _integer constant expression_ shall have integer type and shall only
    have operands that are integer constants, enumeration constants,
    character constants, sizeof expressions whose results are integer
    constants, _Alignof expressions, and floating constants that are the
    immediate operands of casts. Cast operators in an integer constant
    expression shall only convert arithmetic types to integer types, except
    as part of an operand to the sizeof or _Alignof operator." 6.6p6

    As a result, the third version of port_a cannot be used for any of the
    following purposes:
    1. The dimension of an array with static storage duration.
    2. The size of a bit-field.
    3. The value of an enumeration constant.
    4. The argument of an _Alignas() specifier.
    5. The subscript of an array element designator in a designated initializer.
    6. The controlling expression of a _static_assert().
    7. The expression in a case: label.

    I think it should be pretty easy to avoid using port_a in any of those

    In C++, because port_a is const and initialized with an integer constant
    expression, port_a can qualify as an integral constant expression
    itself, and can therefore be used in all of those locations. I know of
    no reason why C couldn't adopt the same rule, and I think it would be a
    good idea - but it hasn't been done yet.
    No, it is not modifiable by any means.
    There's no point in worrying about it being modified. The behavior of a
    program that attempts to modify the value of an object that has been
    declared const is undefined (6.7.3p6). One of the most likely
    possibilities is the value will not change. Even if it does change, that
    fact will be the least of your worries, if you execute such a program.
    I normally use #define in virtually all contexts. In C++, I would use
    the const approach, because it has better type safety, and can be given
    class scope.
    The "static const" approach is intended for use in a header file. Since
    the value must be explicitly given, it necessary qualifies as a
    definition for the object, and therefore provides a separate definition
    in each translation unit where the header is #included. Therefore, if it
    had external linkage, your program would have undefined behavior due to
    providing multiple definitions for the same object. By using the static
    keyword, it has internal linkage, and is therefore a separate object in
    each translation unit. This might seem to be a waste of space, but
    unless the address of the object is taken and used somewhere, no actual
    object of that type needs to be created. Any decent compiler should drop
    it if it can (for instance, if port_a never gets used anywhere in a
    given translation unit).
    There is a convention that such identifiers should be in all-caps - I
    think that following that convention is a good idea.
    James Kuyper, Jun 2, 2014
  9. James Harris

    David Brown Guest

    The static const will work fine for compile-time expressions (though
    with the limitations you noted that they can't be used for array bounds
    or case labels).

    Since the OP referred to "PORT_A", I have been assuming he is talking
    about microcontroller programming and accessing memory-mapped GPIO
    registers. It is conceivable, but highly unlikely, that these would be
    used for case labels - and even less likely that they would be used for
    array bounds.

    So you are technically correct, and the limitations are worth
    mentioning, but I all three methods will work fine for the typical uses
    of such constants in this (assumed) context.
    Yes, the different methods all have their advantages and disadvantages.
    I don't agree that enum is "the best of the bunch" - it feels too much
    of an abuse of the language to me. But it is sometimes the least bad

    (The best, of course, would be for C to allow "static const" values to
    be used in case labels and array sizes. But that would be a change in
    the language.)
    I agree that type names like ui16 will be portable if they are defined
    in terms of uint16_t, but in practice people seldom do so. They usually
    come from historic pre-C99 code, or code with a "portability" header
    that defines target and compiler specific types and constants. After
    all, if you are using <stdint.h>, why would you write "typedef uint16_t
    ui16;" when you can just as easily use the standard types? If you think
    saving a couple of characters improves your code, you've got big
    problems in your development processes.

    There are occasions when home-made types like "ui16" make sense (parts
    of the Linux kernel use types in that line), and it can make sense to be
    consistent with other historic code that uses them - consistency is
    important. But in general, for new code, you should stick to the
    standard types.
    David Brown, Jun 2, 2014
  10. James Harris

    James Kuyper Guest

    I was in a hurry, and went a little overboard there. When I have a group
    of closely related integer constants, and either they all fit in the
    range 0-65535 or they all fit in the range -32767 to 32767, I will
    usually declare them as enumeration constants of the same enumeration
    type. This serves mainly for documentation purposes: it documents that
    those constants are related to each other, and anything declared as
    having that enumeration type is implicitly documented as having those
    values, and no others, as valid values - though there is no
    language-level enforcement of that idea.

    I'm particularly likely to do this if the values are consecutive
    integers starting with 0, because it makes it easy to automate the
    counting of the enumeration constants, as in the following example:

    typedef enum {
    } resolution_index_t;

    extern int32 subsamples_at_res[NUM_L1A_RESOLUTIONS];
    James Kuyper, Jun 2, 2014
  11. The point of the #define is to give the value a name in the source code.
    Comments are good, but they very commonly get out of sync with the
    actual code.

    You wrote:

    int main()
    /* use Ox74 for port A */
    intitialiseIO(0x74, "An argument", "Another setting");

    You already have a typo (Ox74 rather than 0x74); what if you had written
    0x73 rather than 0x74? What if the value changes and you update one
    occurrence but not the other? Using a #define lets you write the
    relevant value just once.
    I'm trying and failing to make sense of that.
    So let the subroutine (or a function as we call it in C) refer to the
    #defined value.
    Keith Thompson, Jun 2, 2014
  12. Correction: it doesn't deliver a *constant* expression. "const" is a
    keyword that means, more or less, read-only. "Constant" refers to
    expressions that can be, and in many cases must be, evaluated at compile
    time (or at least before execution time). The similarity of the terms
    can be confusing, but they're quite distinct concepts.
    Of the comp.lang.c FAQ, http://www.c-faq.com

    Keith Thompson, Jun 2, 2014
  13. What's the advantage of using "ui16" rather than the standard "uint16_t"?
    Keith Thompson, Jun 2, 2014
  14. An expression like PORTA/2 might well break is PORTA is odd. That can happen
    if ports is a variable as well, but the programmer is much more likely to test
    and be aware if port sis a variable.
    writeasciiz(int port, char *str)

    can be called on port a and port b, from the same program.

    writeasciiztoporta(char *str)
    writebytes(PORTA, strlen(str) +1);

    cannot be. So the first function is preferable. It's better to define port A in one place,
    then pass it down to the subroutines. This isn't always feasible, for various reasons,
    but it's the strategy to adopt where possible.
    Also the second function has a dependency on the #define. It's not modular.
    Malcolm McLean, Jun 2, 2014
  15. James Harris

    James Kuyper Guest

    It's entirely reasonable to wonder whether ui16 might be a typedef for
    uint_least16_t, uint_fast16_t, or even unsigned short. No two of those
    types are guaranteed to be the same, so it matters which of the four it
    actually refers to. You can find out, of course, by looking at the
    typedef, but if he had used uint16_t, there would have been no need to
    look it up.
    James Kuyper, Jun 2, 2014
  16. James Harris

    James Kuyper Guest

    It allows him to use a name he likes better than the one provided by the
    standard. That's more important to him than making it easier for
    maintenance programmers to understand what's going on. It's essentially
    the same reason some people do

    typedef unsigned int uint;
    James Kuyper, Jun 2, 2014
  17. James Harris

    Richard Bos Guest

    That's an assertion, not a proof. And I counter-assert: a programmer is
    more likely to check such things if he has the value in front of him
    than if he starts with only a variable name.

    Richard Bos, Jun 2, 2014
  18. James Harris

    BartC Guest

    There would be no need to look it up anyway, as it wouldn't occur to anyone
    who's not a C expert that it might be uint_least16_t or uint_fast16_t
    (whatever they might mean).

    Although I will concede that someone using one of those last two is more
    likely to want to use a short, snazzy typedef instead of cluttering up code
    with uinit_least16_t everywhere [typo deliberately left in].

    Some of us feel the same about uint16_t (I would use u16 when I needed
    something short).
    BartC, Jun 2, 2014
  19. James Harris

    James Harris Guest

    Hard to say. Sometimes sticking to common conventions makes code more
    legible. On one hand I prefer all caps for constants because that is
    idiomatic and thus easy to recognise. On the other hand there is a semantic
    similarity between some constants and the parameters of a function. Indeed,
    what is a constant in one piece of code can become a parameter in a later
    version and vice versa.

    Sometimes I wonder if it wouldn't be better to use all caps consistently for
    just macros so that the dangers of their use become more obvious.
    Not always.... One compiler I use does not have stdint definitions. I could
    define my own stdint types but to me those names should be left for the
    standard headers to use. Using my own names makes clear that these are not

    Further, a later version of that compiler could include stdints. Keeping to
    my own names makes my code independent of such changes.
    I know what you mean but in this case port_a is an IO port and is not memory
    mapped so that would not work. The 16-bit number for port_a eventually gets
    passed to an assembly routine which reads or writes the port with an in or
    out instruction.

    James Harris, Jun 2, 2014
  20. If you assume an non-human programmer, then it really doesn't matter what
    language or software development methods you use.
    If you assume a human programer, you've got to accept that it's difficult to
    prove anything about how he is likely to behave.

    My experience is that changing a #define to a different value is highly likely to break
    something. Passing a different value to a parameterised function, seldom breaks
    that function, except for degenerate cases like INT_MIN.
    You saw my code break with RAND_MAX


    #define uniform (rand() / (RAND_MAX + 1.0))

    i = uniform() % N;

    breaks for certain reasonable values of RAND_MAX.
    Malcolm McLean, Jun 2, 2014
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.