Good C programming style

S

Sensei

Hi!

I'm thinking about a good programming style, pros and cons of some
topics. Of course, this has nothing to do with indentation... Students
are now java-dependent (too bad) and I need some serious motivations
for many issues... I hope you can help me :) I begin with the two major
for now, others will come for sure!

- function variables: they're used to java, so no pointers. Personally
I'd use always pointers, but why could be better to use:
void myfun(int &i) or (int *i) or (int i)
given that the function can and cannot (the last case of course) modify
a parameter. I see performance issues (stack related)...

- defines: why they use function and variable defines? why they shouldn't?
#define MY_PI 3.1415
#define fun(x) ((x) * (x))
 
M

Mike Wahler

Sensei said:
Hi!

I'm thinking about a good programming style, pros and cons of some topics.
Of course, this has nothing to do with indentation... Students are now
java-dependent (too bad) and I need some serious motivations for many
issues...

So you're teaching C?
I hope you can help me :) I begin with the two major for now, others will
come for sure!

- function variables: they're used to java, so no pointers. Personally I'd
use always pointers, but why could be better to use:
void myfun(int &i)

but you don't appear able to distinguish C from C++
or (int *i) or (int i)
given that the function can and cannot (the last case of course) modify a
parameter. I see performance issues (stack related)...

and you don't appear to be aware of the consensus about
premature optimization
- defines: why they use function and variable defines? why they shouldn't?
#define MY_PI 3.1415
#define fun(x) ((x) * (x))

and you don't appear to be aware of the type-checking that a
real function can give, and that macros do not.


Running quickly in the other direction,
-Mike
 
S

Skarmander

Sensei said:
I'm thinking about a good programming style, pros and cons of some
topics. Of course, this has nothing to do with indentation... Students
are now java-dependent (too bad) and I need some serious motivations for
many issues... I hope you can help me :) I begin with the two major for
now, others will come for sure!
The questions you're asking are elemental. Read a good book on C and
these topics should be treated in some detail, along with explanations
that will help you decide on what is appropriate and why.
- function variables: they're used to java, so no pointers. Personally
I'd use always pointers, but why could be better to use:
void myfun(int &i) or (int *i) or (int i)

int &i is not C. You may be thinking of C++.

As for when to use pointers and when not, it's simple: pointers for
variable-sized data like strings, pointers also to implement
call-by-reference in C, that is, arguments you'll need to change.
Pointers also for structs, see below. And finally, pointers for
functions, since there's no other way to pass them.
given that the function can and cannot (the last case of course) modify
a parameter. I see performance issues (stack related)...
Mostly irrelevant. A C idiom is to always pass structure types by
pointer because passing most structures by value means copying, which is
indeed a waste of time in most cases, especially if the value isn't
modified. While a compiler could optimize this in some cases, it's not
expected to, and programmers don't count on it.
- defines: why they use function and variable defines? why they shouldn't?
#define MY_PI 3.1415

Established C practice is to use #define for constants since C is a bit
puzzling when it comes to constants. Declaring a variable "const" does
not make its value a constant expression for purposes of array sizes,
for example. Also, memory is allocated for const variables (great
oxymoron, incidentally :) when it typically isn't necessary.

There's no such thing as a "function" or "variable" define, however.
Macros just establish textual replacement rules.
#define fun(x) ((x) * (x))
Use functions when you can, macros when you have to. The example above
is a case of when you don't have to.

As for when you need macros, that's not a matter of coding style.

S.
 
K

Keith Thompson

Sensei said:
I'm thinking about a good programming style, pros and cons of some
topics. Of course, this has nothing to do with indentation... Students
are now java-dependent (too bad) and I need some serious motivations
for many issues... I hope you can help me :) I begin with the two
major for now, others will come for sure!

- function variables: they're used to java, so no pointers. Personally
I'd use always pointers, but why could be better to use:
void myfun(int &i) or (int *i) or (int i)
given that the function can and cannot (the last case of course)
modify a parameter. I see performance issues (stack related)...

The term "function variables" is misleading; you mean parameters (the
things declared between the parentheses in a function declaration) or
arguments (the actual values passed in a function call).

The declaration "void myfun(int &i)" is not valid C. (I think it's
C++; see comp.lang.c++.)

The choice between "void myfun(int *p)" and "void myfun(int i)" isn't
really a matter of style; it's just a matter of what the function
needs to do. (Note that I've changed the name of the pointer
parameter; calling a pointer "i" is misleading.) If myfun needs to
change the value of an object specified by the caller, or if you want
to point to an array of integers, use a pointer. If you just want to
pass an integer value, use an int.

Note that if myfun's parameter is a pointer, you can only pass the
address of an ojbect (or a null pointer); you can't pass 42 or x+3.

If the parameter is of a struct type, it often makes sense to pass a
pointer even if you don't want to modify the struct object. Passing
an argument means making a copy of the argument and storing it in the
parameter; for large structures, this can be significantly expensive.
This consideration doesn't apply to ints, since there's no significant
performance difference between passing a copy of an int and passing a
copy of its address.
- defines: why they use function and variable defines? why they shouldn't?
#define MY_PI 3.1415
#define fun(x) ((x) * (x))

For MY_PI (BTW, everybody else's PI is closer to 3.1416), C doesn't
provide a mechanism for defining a constant floating-point value. You
can declare
const double my_pi = 3.14159265358979323848264;
but that creates a write-only variable, not a real constant. Macros
are useful for this kind of thing.

As for function-like macros, they're sometimes the best way to create
inline functions. (C99 has inline functions, but not all compilers
support them.) There are also things you can do in a macro that you
can't easily do in a function, such as using a type name as an
argument. But try not to be too tricky. Macros can be dangerous,
since they're expanded in a very early stage of compilation. They can
*look* like function calls or variable declarations, but they don't
necessarily act like them; for example, they pay no attention to
scope.
 
S

Skarmander

Keith Thompson wrote:
You can declare
const double my_pi = 3.14159265358979323848264;
but that creates a write-only variable, not a real constant. Macros
are useful for this kind of thing.
You know, a write-only variable is about the only thing C's missing. :-D

S.
 
F

Flash Gordon

Sensei said:
Hi!

I'm thinking about a good programming style, pros and cons of some
topics. Of course, this has nothing to do with indentation... Students
are now java-dependent (too bad) and I need some serious motivations for
many issues... I hope you can help me :) I begin with the two major for
now, others will come for sure!

- function variables: they're used to java, so no pointers. Personally
I'd use always pointers, but why could be better to use:
void myfun(int &i) or (int *i) or (int i)

void myfun(int &i) is an error in C. The two options that are C that you
list are
void myfun(int *i)
void myfun(int i)
given that the function can and cannot (the last case of course) modify
a parameter. I see performance issues (stack related)...

C does not require a stack, but assuming a stack implementation in
either case something will be pushed on to it, either the value of an
int or a pointer to an int. Do you *really* think either will have a
significant performance difference?

Write what you mean, don't try to micro-optimise.
- defines: why they use function and variable defines? why they shouldn't?
#define MY_PI 3.1415

How else would you define MY_PI? Apart from more accurately, that is ;-)
#define fun(x) ((x) * (x))

In this instance fun will work on any arithmetic type which can
sometimes be an advantage. I would generally recommend using functions
rather than function like macros *except* where you have a specific
reason to need a function like macro (e.g. if you wanted fun above to
operate on any arithmetic type and not have it convert everything to
double or long double).

Macro's can have unexpected effects such as given the above definition
if you do
fun(a++)
you actually invoke undefined behaviour because it will expand to
((a++) * (a++))
which attempts to modify a twice without an intervening sequence point.
This is why you should not use function like macros except where you
need them. It is also why you should follow the convention of spelling
macros in UPPER CASE to give people an immediate warning.
 
J

Joe Wright

Skarmander said:
Keith Thompson wrote:

You know, a write-only variable is about the only thing C's missing. :-D

S.

The write-only characteristic is priceless. In a former life I designed
a ram-disk subsystem, memory which should 'look' like a disk drive.
Until I got it working, I referred to it as write-only memory.
 
G

Gregory Pietsch

Sensei said:
Hi!

I'm thinking about a good programming style, pros and cons of some
topics. Of course, this has nothing to do with indentation... Students
are now java-dependent (too bad) and I need some serious motivations
for many issues... I hope you can help me :) I begin with the two major
for now, others will come for sure!

- function variables: they're used to java, so no pointers. Personally
I'd use always pointers, but why could be better to use:
void myfun(int &i) or (int *i) or (int i)

int &i is not standard C.
given that the function can and cannot (the last case of course) modify
a parameter. I see performance issues (stack related)...

- defines: why they use function and variable defines? why they shouldn't?
#define MY_PI 3.1415

You might want to extend pi a few more decimal places. The SUSv3
#define fun(x) ((x) * (x))

This is considered bad style for a macro because the macro argument is
evaluated twice. Imagine what would happen if the argument to the macro
contained an increment or decrement operator.

Gregory Pietsch
 
S

Skarmander

Joe said:
The write-only characteristic is priceless. In a former life I designed
a ram-disk subsystem, memory which should 'look' like a disk drive.
Until I got it working, I referred to it as write-only memory.
I have actually worked on machines that had write-only memory, typically
memory-mapped hardware (the addresses corresponding to "out" ports of
some sort, of course). Aside from the curiousity value, it's really not
that different from regular memory -- you won't use it to store
temporaries or read back what you've written, after all.

S.
 
S

Sensei

So you're teaching C?


Nope. I'm an engineer working on theoretics. I *have* to teach C, and
I'm studying before doing anything wrong.

but you don't appear able to distinguish C from C++


Correct. I'm trying.

and you don't appear to be aware of the consensus about
premature optimization


My biggest programming efforts have always been on assembly. I'm used
to do a shift instead of dividing or multiplying by 2. I've read it's
not useful on high level languages like C.

If a variable is passed on the stack, I imagine *i pushes an address,
while i pushed the entire data, so if i is a big structure is not a
nice thing.

Moreover, I don't know if C says *how* parameters are passed to
functions: stack, pointer, whatever...

and you don't appear to be aware of the type-checking that a
real function can give, and that macros do not.


Of course macros are macros. No checks, no functions. Why using one
instead of another is not clear to me. As I said, I'm an assembly guy,
and ``functions'' do not really exist.

You don't appear to understand that people could ask for informations
here about std-c with different knowledge :)
 
S

Sensei

The questions you're asking are elemental. Read a good book on C and
these topics should be treated in some detail, along with explanations
that will help you decide on what is appropriate and why.


As I said in another post, I'm studying now.
Mostly irrelevant. A C idiom is to always pass structure types by
pointer because passing most structures by value means copying, which
is indeed a waste of time in most cases, especially if the value isn't
modified. While a compiler could optimize this in some cases, it's not
expected to, and programmers don't count on it.

Clear.



Established C practice is to use #define for constants since C is a bit
puzzling when it comes to constants. Declaring a variable "const" does
not make its value a constant expression for purposes of array sizes,
for example. Also, memory is allocated for const variables (great
oxymoron, incidentally :) when it typically isn't necessary.

There's no such thing as a "function" or "variable" define, however.
Macros just establish textual replacement rules.


Yes but...
Use functions when you can, macros when you have to. The example above
is a case of when you don't have to.

As for when you need macros, that's not a matter of coding style.

....this is puzzling to me. I've seen functions replaced by macros, like this:

#define xge_mklink_slot(com,from,to,slotfrom,slotto) \
{\
tkey _slotfrom=(slotfrom);\
tkey _slotto =(slotto );\
\
CHECKPOINT("ie",getcell((com),(from)).level!=0xff &&
getcell((com),(to)).level!=0xff);\
\
if (!_slotfrom)\
{\
if (!(com)->memlink.freelist) realloc_link((com));\
_slotfrom=(com)->memlink.freelist;\
(com)->memlink.freelist=getlink((com),_slotfrom).next;\
++((com)->memlink.num);\
\
if (!getcell((com),(from)).nup)\
getcell((com),(from)).firstup=_slotfrom;\
else\

getlink((com),getcell((com),(from)).lastup).next=_slotfrom;\
\
getlink((com),_slotfrom).next=0;\
getlink((com),_slotfrom).prev=getcell((com),(from)).lastup; \
getcell((com),(from)).lastup=_slotfrom;\
++getcell((com),(from)).nup;\
}\
\
if (!_slotto)\
............


So, I was wondering why and when it's a good idea to use defines.
 
S

Sensei

As for function-like macros, they're sometimes the best way to create
inline functions. (C99 has inline functions, but not all compilers
support them.) There are also things you can do in a macro that you
can't easily do in a function, such as using a type name as an
argument. But try not to be too tricky. Macros can be dangerous,
since they're expanded in a very early stage of compilation. They can
*look* like function calls or variable declarations, but they don't
necessarily act like them; for example, they pay no attention to scope.


Ok, so defines are used to improve performances as inline functions are
not supported everywhere.
 
R

Richard Heathfield

Gregory Pietsch said:
You might want to extend pi a few more decimal places.

Why? Observe:

Circumference of Earth (plus or minus a bit): 40000000 metres.
Pi = 3.1415
Radius of Earth: 6366385 metres

Let's add 1 dp.

Circumference of Earth (plus or minus a bit): 40000000 metres.
Pi = 3.14159
Radius of Earth: 6366203 metres

Difference from last result: 182 metres. If you're calculating fuel
requirements for a trip round the entire planet, you'd factor in a
contingency anyway; you're not going to crash for the sake of 182 metres.

Let's add another.

Circumference of Earth (plus or minus a bit): 40000000 metres.
Pi = 3.141592
Radius of Earth: 6366199 metres

Difference from last result: 4 metres. Good enough for nuclear war.

Let's add another.

Circumference of Earth (plus or minus a bit): 40000000 metres.
Pi = 3.1415926 (still comfortably within the ability of a pocket calculator)
Radius of Earth: 6366198 metres. Difference from last result: 1 metre. Good
enough for assassinating one mad dictator (or one troublesome priest) from
the other side of the planet.

Let's add another.

Circumference of Earth (plus or minus a bit): 40000000 metres.
Pi = 3.14159265
Radius of Earth: 6366198 metres. Difference from last result: 0 metres.
(Okay, so I'm rounding - but the difference is less than 1 metre, that's
the point.)

So who gives a stuff? :)
 
F

Flash Gordon

Sensei said:
Nope. I'm an engineer working on theoretics. I *have* to teach C, and
I'm studying before doing anything wrong.

Well, I hope you have plenty of time, since you have a *lot* to learn.
Correct. I'm trying.

Try to learn one language at a time. Trying to learn two, especially two
with a lot of similarities, is bound to lead to confusion.
My biggest programming efforts have always been on assembly. I'm used to
do a shift instead of dividing or multiplying by 2. I've read it's not
useful on high level languages like C.

What you have read is correct. Modern compilers have an optimiser to do
this for you.
If a variable is passed on the stack, I imagine *i pushes an address,
while i pushed the entire data, so if i is a big structure is not a nice
thing.

Yes, that is correct. With a large structure you would tend to pass a
pointer to avoid copying a lot of data.
Moreover, I don't know if C says *how* parameters are passed to
functions: stack, pointer, whatever...

C does not specify a stack. All it specifies is that parameters are
passed by value (even pointers). On at least some systems parameters
will be passed in registers.
Of course macros are macros. No checks, no functions. Why using one
instead of another is not clear to me. As I said, I'm an assembly guy,
and ``functions'' do not really exist.

Type checking is a wonderful thing, it allows the compiler to complain
if you do something obviously stupid. Such as passing a pointer to a
function requiring an integer.
You don't appear to understand that people could ask for informations
here about std-c with different knowledge :)

We are quite used to that.

I strongly suggest you work through K&R2 and read the FAQ for this group
(which will explain what K&R2 is).

Also remember that high level languages were invented to make
programming easier, and this means you should forget about using a lot
of the little tricks you use as an assembler programmer.
 
P

pete

Sensei wrote:
So, I was wondering why and when it's a good idea to use defines.

If a #define can make the source code easier to read,
then that is why and when.
 
S

Skarmander

Sensei said:
...this is puzzling to me. I've seen functions replaced by macros, like
this:
#define xge_mklink_slot(com,from,to,slotfrom,slotto) \
{\
tkey _slotfrom=(slotfrom);\
tkey _slotto =(slotto );\
\
CHECKPOINT("ie",getcell((com),(from)).level!=0xff &&
getcell((com),(to)).level!=0xff);\
\
if (!_slotfrom)\
{\
if (!(com)->memlink.freelist) realloc_link((com));\
_slotfrom=(com)->memlink.freelist;\
(com)->memlink.freelist=getlink((com),_slotfrom).next;\
++((com)->memlink.num);\
\
if (!getcell((com),(from)).nup)\
getcell((com),(from)).firstup=_slotfrom;\
else\

getlink((com),getcell((com),(from)).lastup).next=_slotfrom;\
\
getlink((com),_slotfrom).next=0;\

getlink((com),_slotfrom).prev=getcell((com),(from)).lastup; \
getcell((com),(from)).lastup=_slotfrom;\
++getcell((com),(from)).nup;\
}\
\
if (!_slotto)\
...........
This is absolutely horrible. Words fail me. Whoever wrote this should be
shot.

Let's go over why this is a bad idea:

- In a macro, arguments are not checked against their types, as there
are no types. This means that the function is both harder to call ("com"
must be a pointer, but of what type?) and the compiler can offer less
help checking validity.

- Generalizing, the validity of a macro simply cannot be checked on its
own. Only uses of it can be checked. Errors or warnings that occur from
such a use could be either due to bad use or a bad macro, and it'll be
hard to tell which is which.

- Unlike functions, macros use textual replacement for their arguments.
If an argument uses a side-effect (it modifies the program state when
evaluated) then you typically cannot use it. For example:
xge_mklink_slot(c, f++, t++, slf, slt);
This does not have the same effect as the equivalent function call
would: every time "from" is replaced in the macro it is replaced with
"f++". That is, f will be incremented every time "from" is used!

- This is very likely to *harm* performance. Yes, you read that right.
If this macro is invoked multiple times, the great big block of code it
stands for is inserted *every time*. Do this a few times and you'll get
code that is huge. And WHY? Function calls are not nearly as expensive
as most people like to pretend. It is in fact likely this code will run
slower because big code means the instruction cache most modern
processors are equipped with cannot be effectively used. Never try to
outsmart the compiler. Only educate it in things you know it cannot see.
Making decisions on when to inline functions is something a compiler is
qualified to do, and only code profiling should be admissible evidence
to the contrary.

Apropos inlining: almost all compilers I know offer a way to indicate
that inlining is desirable (or even force it where possible). The new
C99 standard adds an "inline" keyword, and most pre-C99 compilers
support an "inline", "_inline", "__inline" keyword or some variation
thereof. Macros are almost never necessary or desirable to get an
inlined function.

All that said, there is one purpose this macro could still serve that
cannot be achieved otherwise in C: precisely because the arguments have
no types, it can be used as a template to produce similar functions that
differ only in the type of the arguments. This is what templates
accomplish in C++ and generics accomplish in the most recent versions of
Java.

But even so, it should only be used in a way that does not compromise
type safety, by defining the type-safe functions:

/* Unlike XGE_MKLINK_SLOT, the compiler will warn or stop if the
argument types do not match the formal parameter types. */
[return-type] xge_mklink_slot(xge_com* com, xge_cell from, xge_cell to,
tkey slotfrom, tkey slotto) {
return XGE_MKLINK_SLOT(com, from, to, slotfrom, slotto);
}

Here [return-type] I cannot know, and the xge_com and xge_cell types are
invented by me. I capitalized XGE_MKLINK_SLOT because macro names
*should* be capitalized to prevent confusion: as I've said, using a
macro is not the same thing as calling a function, and it's dangerous to
pretend that it is.

Even this technique is dubious because xge_mklink_slot is a highly
specific macro, and it's unlikely it's used in the way I sketched above.
Note also that in C, generic functions are usually written by passing
around generic pointers. Consult your C book for more information; it
will probably mention the qsort() function as an example.

This macro *should* be rewritten to a function. I cannot conceive of any
reason why a macro would be preferrable in this case. Do you understand
why I said it's not a matter of coding style? It's a matter of what you
want to achieve, not how it looks. "Style", if it comes into play at
all, is to prefer functions to macros consistently, and being ready to
justify your use of a macro.
> So, I was wondering why and when it's a good idea to use defines.
>
Don't think in terms of "good idea/bad idea". Learn what purpose and
what drawbacks each feature has, then apply this knowledge in every case
to achieve the best result. In case of macros, the drawbacks associated
with them very often outweigh the benefits, especially when it comes to
replacing functions.

I can give you the short version, of course, but keep in mind that these
are only rules of thumb and not absolute commandments. Use a macro for:

- defining symbolic constants that are not part of an enumerated type.
Example: #define M_PI 3.1415926536
- encapsulating system-specific constructs that cannot be abstracted by
a function. Example: #define INLINE __inline
- allowing header files to be included more than once without error.
Example:
#ifndef HEADER_H
#define HEADER_H
...contents of "header.h"
#endif

These are the basic established "good ideas" for macros. There are other
circumstances in which experienced C programmers will use macros, but
you should leave them until you can understand why. I won't attempt to
explain it all in a Usenet post; consult the books written by people
who've had more time than me to think about it.

S.
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,015
Latest member
AmbrosePal

Latest Threads

Top