A common C++ trick also works in C, and a longtime CUJ reader reminds Pete that many programmers do as well.
Q
I read your column in the C/C++ Users Journal in October on macro substitutions in C. To some degree, much of the C I see is pretty bad. If people can't write clean C, how can they write C++? (I don't know much about C++ except how to spell it and how to compile applications with g++.) I think it would help to see "this is how to write clean C" rather than "drop C and switch to C++."The code snippet
#define BUFSIZE 100 char buf[BUFSIZE] money(i, buf, BUFSIZE);
shows a number of things wrong with much of the code I see. Anyway, in this context, always use the sizeof operator. In fact, adding tokens pollutes; in this case its unnecessary.
char buf[100] money(i, buf, sizeof(buf));
If you want to use a constant (only once), it makes sense to do this:
#ifndef BUFSIZE #define BUFSIZE 100 #endif char buf[BUFSIZE]; money(i, buf, sizeof(buf));
In this case, you can change BUFSIZE at compile time.
Doug McIlroy turned me on to the following style. I found it strange at first, then asked him about it, and he explained it (it's good practice and it saved space in the listing). Use enums to define manifest constants!!
enum { BUFSIZE = 100 }; char buf[BUFSIZE]; money(i, buf, sizeof(buf))
The advantage to using enums is:
- the debugger sees them (gdb)
- the scope is within the block
Maybe everyone wants to write C++.
'Tis a pity they forgot about C. (I think C can be a
wonderful way to express computer programs because it's so
simple.) And I've been using OO in C for 15 years. (I
normally decompose problems into data abstractions and
methods.)
Marty Leisner
A
In the narrow context of
#define BUFSIZE 100 char buf[BUFSIZE] money(i, buf, BUFSIZE);
it makes good sense to use sizeof, rather than make the size of the buffer a manifest constant. You have to be careful about using sizeof with arrays, though: be sure that you use it only at a point in the program where you are dealing with the actual array and not a pointer to its first element. That's why the money function cannot simply figure out the size of the buffer for itself:
void money( int value, char *buf ) { /* incorrect! */ size_t buf_len = sizeof buf; }
In this snippet, buf is a pointer to char, and sizeof buf is simply the size of a pointer. That has nothing to do whatsoever with the number of elements in the array, and that's why I hesitate to recommend the use of sizeof when dealing with arrays. There are quite a few C programmers who are confused about the difference between an array and a pointer, and for these people, using sizeof would lead to serious mistakes.
Your suggested use of an enum has been enshrined in C++ history as "the enum hack." Until recently, C++ had no direct way of defining an integral compile-time constant with class scope. That made it hard to write definitions of classes that contained fixed-size arrays, unless you were willing to hard-code the size of the array:
class C { int buf[10];//hardcoded size public: C(); };
In itself, the above isn't bad, but the code to initialize the array gets a bit longwinded because it has to use sizeof rather than a named constant:
C::C() { for( int i = 0; i < sizeof array; i++ ) buf[i] = i; }
Then along came the enum hack, and things were much cleaner:
class C { enum { BUFSIZE = 10 }; int buf[BUFSIZE]; public: C(); }; C::C() { for( int i = 0; i < BUFSIZE; i++ ) buf[i] = i; }
This approach amounts to a slight abuse of the enumerated type, mostly because the resulting "constant" isn't used as a type at all. The enum hack does have one significant thing going for it: it works, and in the absence of a better solution that's enough to make it a common technique.
I haven't seen the enum hack applied in C, nor have I taken the time to assess its usefulness in that language. In general, though, C programmers know how to stay out of the way of already-defined macros, so using #define to create a manifest constant doesn't cause significant problems. I suspect that's why I haven't seen the enum hack used.
As for C++, it puts much more emphasis on scopes, and blasting through scopes with a #define is definitely something that you want to keep to a minimum. Abuse of #define is what led to the use of the enum hack in C++, until a better solution came along: now the language definition supports compile-time constants within classes. Now you can write:
class C { const int BUFSIZE = 10; int buf[BUFSIZE]; public: C(); }; const int C::BUFSIZE; C::C() { for( int i = 0; i < BUFSIZE; i++ ) buf[i] = i; }
I realize I just did what you lamented in your letter: I changed the discussion from C to C++. As I said earlier, I haven't seen the enum hack used in C, so it's hard for me to come up with meaningful examples of its use in C. In C++ it was a handy trick, but pretty much everyone agreed that it was ugly, and now that there's a better way to handle class-scoped integral constants it is fading into the pages of history.
To ask Pete a question about C or C++, send e-mail to [email protected], use subject line: Questions and Answers, or write to Pete Becker, C/C++ Users Journal, 1601 W. 23rd St., Ste. 200, Lawrence, KS 66046.
Pete Becker is a senior development manager at Borland International, working in Borland's Middleware and Application Management Development group in Boston, Mass. He has been actively involved in C++ development work at Borland for seven years, both as a developer and a manager. He is Borland's representative to the ANSI/ISO C++ standardization committee. He can be reached by e-mail at [email protected].