9

I'm working on some code for a microprocessor.
It has a few large, critical constants.

#define F_CPU 16000000UL

In this case, this is the CPU frequency. In Hertz.

As it is, it's rather hard to tell if that's 1,600,000, 160,000,000 or 16,000,000 without manually tabbing a cursor across the digits.

If I put commas in the number #define F_CPU 16,000,000UL, it truncates the constant.

I've worked with a few esoteric languages that have a specific digit-separator character, intended to make large numbers more readable (ex 16_000_000, mostly in languages intended for MCUs). Large "magic numbers" are rather common in embedded stuff, as they are needed to describe aspects of how a MCU talks to the real world.

Is there anything like this in C?

Fake Name
  • 5,556
  • 5
  • 44
  • 66
  • The term "magic number" usually refers to numeric constants suddenly appearing in the middle of the code. Ie something like: `if (var == 16000000) ...` (bad), rather than `if (var == F_CPU) ...` (good). – Lundin Jun 12 '12 at 14:43
  • Would it help to have something like: #define F_CPU NUM_GROUPED_3ARGS(16,000,000UL) ? https://stackoverflow.com/questions/10977260/making-large-constants-in-c-source-more-readable/50580598#50580598 – grenix May 29 '18 at 12:16

8 Answers8

11

Yes, C does have preprocessor separators: ##

So you can write

#define F_CPU 16##000##000UL

which has exactly the same meaning as 16000000UL. (Unlike other structures like 16*1000*1000 where you need to be careful not to put them in places where the multiplication can cause problems.)

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
  • 1
    In what cases might multiplication cause problems? – jamesdlin Jun 11 '12 at 10:27
  • @jamesdlin Well, in expressions with higher precedence operators. For example if you have `#define FIRST 16000000` and `#define SECOND 16*1000*1000` without parenthesis, `~FIRST` would not be the same as `~SECOND`. – Mr Lister Jun 11 '12 at 11:42
  • Okay, then I'd qualify instead that as "need to be careful to parenthesize the expression", which any sane C programmer normally would do anyway. – jamesdlin Jun 11 '12 at 11:53
  • @jamesdlin True, as is the case in most of the answers here. But not all. – Mr Lister Jun 11 '12 at 12:05
  • @MrLister, I was under the impression that for constants, the preprocessor would evaluate the multiplication before doing any sort of expansion. Guess I'll just ask my preprocessor... – JoeFish Jun 11 '12 at 12:39
  • The preprocessor agrees with you, I learned something today. `printf("%X %X\n", ~FIRST, ~SECOND);` `FF0BDBFF FEFC99C0` – JoeFish Jun 11 '12 at 12:41
  • 1
    `##` is actually the [concatenation operator](http://gcc.gnu.org/onlinedocs/gcc-3.2/cpp/Concatenation.html) of the cpp which can be used for other things aswell. – JimmyB Jun 12 '12 at 10:44
6

maybe something like that?

#define MHz(x) (1000000 * (x))
...
#define F_CPU MHz(16)

Also, I don't like #defines. Usually it's better to have enums or constants:

static const long MHz = 1000*1000;
static const long F_CPU = 16 * MHz;
kan
  • 28,279
  • 7
  • 71
  • 101
  • These are resolved out at compile time to set a whole bundle of various constants, that are then written to various hardware registers, and as such set various bit times for serial interfacing, etc... You really don't want to use enums or other stuff, as they may wind up in RAM somewhere. This MCU has a grand total of 2K RAM (and 32K program space), it's rather precious. – Fake Name Jun 11 '12 at 09:40
  • Hell, take a look at this `#define`: `#define Lin_set_btr_brr(bt,br) { U8 __jacq__; __jacq__ = LINCR; LINCR &= ~(1<>1)-1)>>8); LINBRRL = (U8)(( (((((U32)F_CPU*1000)<<1)/(((U32)br)*bt))+1)>>1)-1) ; LINCR = __jacq__; }` – Fake Name Jun 11 '12 at 09:45
  • Note: I didn't write that, it's from the actual MCU documentation. I'm in the process of trying to make it readable. – Fake Name Jun 11 '12 at 09:45
  • @FakeName Yeah, it's insane a bit :) The low-level programming has own rules. – kan Jun 11 '12 at 09:49
  • Your `MHz` macro should parenthesize `x` so that something like `MHz(1 + 2)` behaves properly. – jamesdlin Jun 11 '12 at 10:26
  • @FakeName `enum`s are compile-time constants. They shouldn't behave any differently from integer literals. – jamesdlin Jun 11 '12 at 10:27
  • @jamesdlin yes, you are right about parentheses, I've amended my answer. `enum` however has `int` type, not sure that it's possible to put ULL constant in it. – kan Jun 11 '12 at 10:28
5

One possibility is to write it like that:

#define F_CPU (16 * 1000 * 1000)

alternatively

#define MHz (1000*1000)
#define F_CPU (16 * MHz)

Edit: The MHz(x) others suggested might be nicer

johannes
  • 15,807
  • 3
  • 44
  • 57
  • I actually like this notation better, as it's closer to how the clock would actually be specified. You wouldn't say it's a "MHz(16)" CPU, you would say it a "16 MHz" CPU. – Fake Name Jun 11 '12 at 09:24
  • I wound up using: `#define MHz (1000UL*1000UL)` `#define F_CPU (16 * MHz)` – Fake Name Jun 11 '12 at 09:41
  • I have to specify the numbers as `UL`, otherwise they will get truncated to the MCU native size, which is 8 bits. Which ends badly. – Fake Name Jun 11 '12 at 09:41
  • @Fake Name: `int` can't have size 8 bits on a conforming implementation, it's required to be at least 16 bits. It still ends badly at that size, obviously. – Steve Jessop Jun 11 '12 at 10:53
  • @SteveJessop - That's just it. This *isn't* a completely conforming application. It's a compiler for a **8-bit microprocessor**. The hardware has certain limitations. All multi-byte math has to be cone with multiple byte-math operations, and it seems the compiler is *really* reluctant to do those unless you really explicitly force it. – Fake Name Jun 11 '12 at 21:33
  • 1
    @Fake Name: I suppose I'm mentioning it just because the question is labelled C, but it turns out you aren't programming C, you're programming some other language that is related to C. Unlike the more common variants of C that you get due to minor defects in compilers for PCs and suchlike, most casual passers-by won't know all that much about C-like languages for 8bit processors, and certainly don't know which compiler(s) you need to support, since you haven't said. It might affect the answers. Although not this answer, because even in actual C you need at least a `long` to guarantee 32 bits. – Steve Jessop Jun 12 '12 at 08:35
  • @SteveJessop - Hmm, is there a `C-ish` tag? Anyways, the core of the question is basically straight C. It's just in the comments that I added some details about how I *adapted* the straight-C to this particular C-subset. – Fake Name Jun 12 '12 at 09:08
  • @Fake Name: I think you're OK, that's why I didn't complain about the question initially :-) You're entitled IMO to ask about actual C and then figure out for yourself whether the answers work for your non-conforming compiler. Personally I'd probably tag the question with "C" and also with the name of the compiler (or target, if the compiler is something generic), just in case there's some compiler-specific extension that's significantly nicer than any of the portable answers. "MCUs" could be a few things, but if you only really care about one, name it! – Steve Jessop Jun 12 '12 at 09:15
  • N.b.: [avr-gcc](http://gcc.gnu.org/onlinedocs/gcc-4.4.7/gcc/AVR-Options.html) has the `-mint8` option to force `int` to 8 bits. – JimmyB Jun 12 '12 at 10:37
  • By the way: The preprocessor does not care about the size of numeric datatypes, so the numbers will only be truncated when used/assigned *at runtime*. - Do you need to perform runtime calculations based on the clock frequency? – JimmyB Jun 12 '12 at 10:40
  • -1 This will not work on systems where int is 16 bits, as is the case in the OP's post. 1000 * 1000 will yield an integer overflow, which is undefined behavior. Very common beginner mistake on embedded systems. Please fix this by adding `ul` to each number. – Lundin Jun 12 '12 at 14:25
  • @FakeName Actually the numbers will not get truncated to the MCU native size. Every C compiler I have seen for 8-bit MCUs perform pre-process the calculations as expected, on 16-bit type in this case, then determine whether 16 bits are needed. If not, then the code is optimized down to 8-bit. The C language expects every calculation to be performed on 16 bits or larger, because of the integer promotion rules. This matters, since if there is a bug in the 16-bit calculations, as the overflow bug in this post, then that bug might get "optimized down" to persist in your 8-bit variable values. – Lundin Jun 12 '12 at 14:29
  • ...or perhaps the bug makes it impossible to optimize to 8 bit, since when encountering UB the compiler will make decisions like "aha, here the programmer is relying on an integer overflow to produce some large, weird number, I better not translate this to 8 bit instructions or I'll break his code". Most embedded compilers will work in that manner. – Lundin Jun 12 '12 at 14:45
4

You could write the constant as the result of a calculation (16*1000*1000 for your example). Even better, you could define another macro, MHZ(x), and define your constant as MHZ(16), which would make the code a little bit more self-documenting - at the expense of creating name-space collision probability.

vhallac
  • 13,301
  • 3
  • 25
  • 36
1
// constants.h
#define Hz   1u              // 16 bits
#define kHz  (1000u  *  Hz)  // 16 bits
#define MHz  (1000ul * kHz)  // 32 bits

// somecode.h
#define F_CPU (16ul * MHz)   // 32 bits

Notes:

  • int is 16 bits on a 8 bit MCU.
  • 16 bit literals will get optimized down to 8 bit ones (with 8 bit instructions), whenever possible.
  • Signed integer literals are dangerous, particularly if mixed with bitwise operators, as common in embedded systems. Make everything unsigned by default.
  • Consider using a variable notation or comments that indicate that a constant is 32 bits, since 32 bit variables are very very slow on most 8-bitters.
Lundin
  • 195,001
  • 40
  • 254
  • 396
1

Another aproach would be using the ## preprocessor operator in a more generic macro

#define NUM_GROUPED_4ARGS(a,b,c,d) (##a##b##c##d)
#define NUM_GROUPED_3ARGS(a,b,c)   (##a##b##c)

#define F_CPU NUM_GROUPED_3ARGS(16,000,000UL)

int num = NUM_GROUPED_4ARGS(-2,123,456,789); //int num = (-2123456789);
int fcpu = F_CPU; //int fcpu = (16000000UL);

This is somehow WYSIWYG but not immune against misuse. E. g. you might wnat the compiler to complain about

int num = NUM_GROUPED_4ARGS(-2,/123,456,789);  //int num = (-2/123456789); 

but it will not.

grenix
  • 623
  • 7
  • 15
0

You can use scientific notation:

#define F_CPU 1.6e+007

Or:

#define K 1000

#define F_CPU (1.6*K*K)
Aladdin
  • 171
  • 8
0

It might help readability to define the constant as:

#define F_CPU_HZ 16000000UL

That way you know what type of data is in it. In our SW we have a few peripherals which require assorted prescalers to be set, so we have a #defines like this:

#define SYS_CLK_MHZ    (48)
#define SYS_CLK_KHZ    (SYS_CLK_MHZ * 1000)
#define SYS_CLK_HZ     (SYS_CLK_KHZ * 1000)
John U
  • 2,886
  • 3
  • 27
  • 39