Functions and macros in C

Function headers

There is little difference between function headers in C and method headers in Java; though Java does have a few modifiers, such as public, that wouldn’t make sense in a language like C that isn’t object oriented.

Because early versions of C supported “old-style” function definitions, a C function with no parameters should be declared with a parameter list of void.

int always5(void) {
  return 5 ;
}

In C, the static modifier for a function definition specifies that the function can only be used in the translation unit (roughly the file) in which it is defined. As you know, static means quite a bit more in Java.

Function prototype

In Java, the methods of a class can be defined in any order. This is not the case with C functions. A C function must be declared before it is used. This is usually done with a function prototype. The function prototype is similar to the method definitions found in a Java interfacer. The function prototype specifies the name, parameters and return type of function. However, in C the parameter names can be omitted.

int switchRead(int) ;
void ledWrite(int) ;

You will find function prototypes at the beginning of the C man pages for many C functions. These prototypes will be provided in include files that will also be mentioned in the man page. Here’s an example taken from the man page for C’s cosine functions.

#include <math.h>

double cos(double x);
float cosf(float x);
long double cosl(long double x);

Note that your program should contain only the #include statement, not the actual prototypes.

Programming with prototypes

If you are creating a C (or C++) library, you must create an appropriate .h file for your library. If you are writing a C program, you must include a function’s prototype or definition before a function call.

Macros

One possible implementation for the switchRead routine recently discussed in lab would be the following:

int switchRead(int pin) {
  return digitalRead(pin) == LOW ;
}

This implementation assumes that switch is “on” with the pin input is LOW. However, it is possible to wire the switch so that it is “on” when the input is HIGH. We could add a second parameter to switchRead which specifies when the switch should be considered “on” or we could have two separate functions switchRedLowIsOn and switchRedHighIsOn for the two possibilities or we could use macros.

Changing the switch with a macro definition

The experienced C programmer might use a macro to allow the switchRead to be configured with a #define. Here’s an example in which switchRead is defined with a macro where SWITCH_ONVALUE is used to configure the switch. In this case, by default the switch is considered “on” when SWITCH_ONVALUE is undefined.

#ifndef SWITCH_ONVALUE
#define SWITCH_ONVALUE LOW
#endif

int switchRead(int pin) {
  return digitalRead(pin) == SWITCH_ONVALUE ;
}

This use of macros is ugly, but common.

Avoiding a function call with a macro

Unfortunately, many C programmers will not stop there. They will insist that calling a one-line function is simply too inefficient and replace the function with a macro that accepts arguments.

#if !defined(SWITCH_ONVALUE)
#define SWITCH_ONVALUE LOW
#endif

#define switchRead(_pin)  (digitalRead(_pin) == SWITCH_ONVALUE)

This means that a statement like
    switch_value = switchRead(inputAPin) ;
would be textually transformed into the statement similar to
    switch_value = (digitalRead(inputAPin) == LOW) ;
before the code is compiled to avoid the call overhead of a function invocation.

It all seems a bit much, especially since modern C implementations have a const modifier for variables and an inline modifier for functions which often results in equivalent machine code.

const int switchOnValue = LOW ;

inline int switchRead(int pin) {
  return digitalRead(pin) == switchOnValue ;
}

However, there really isn’t any way to completely avoid macros. Within the Arduino include files, there are macros to define HIGH and LOW.

#define HIGH 0x1
#define LOW  0x0

Too much information on the C Preprocessor