• No se han encontrado resultados

ANÁLISIS DEL CUENTO INFANTIL: La quebrada de Guachalá

ANÁLISIS DE LAS OBRAS REPRESENTATIVAS DE SOLEDAD CÓRDOVA

3.7. ANÁLISIS DEL CUENTO INFANTIL: La quebrada de Guachalá

Symbolic constants defined by the #define command are a simple form of macro: a symbolic name that is expanded into an expression via text substitution. The C preprocessor provides a more sophisticated type of macro definition by allowing the macro name to be followed by a set of arguments enclosed in parentheses. For example,

#define MAX(x,y) ((x)>(y) ? (x) : (y))

1This is a historical anomaly and not a limitation of the ability of the compiler. The C++ language rectifies this oversight and, in C++, aconst intmay define an array size.

Although it looks like a function call, a macro behaves in a quite different manner. The preprocessor replaces the macro name with the defined replacement text, and substitutes the argument variables in the specified locations. Thus,MAXmight be used as follows

int a=4, b= -7, c;

c = MAX(a,b);

and the preprocessor will expand the code to int a=4, b= -7, c;

c = ((a)>(b) ? (a) : (b));

Note. In a macro definition, the parentheses immediately following the macro name must be directly adjacent to the name without whitespace. For example, the definition

#define MAX (x,y) ((x)>(y) ? (x) : (y))

is not equivalent to the previous macro definition, but will simply replace all occurrences of the name MAXwith the text: (x,y) ((x)>(y) ? (x) : (y)).

Macros are typically used for one of two reasons. The first is speed. Macros can perform function-like operations without the overhead of a function call because the code is expanded inline. With modern fast machines, using macros for speed is less important than it used to be. The second use of macros is to implement a kind ofgeneric function. That is, to define a function-like expression that bypasses the C type constraints, and can be passed parameters of any type. For example, the macro MAXwill work correctly ifa, b, andcwere type double, or any other type, where as an equivalent function

int max(int x, int y) {

return x > y ? x : y;

}

will only accept integer parameters.

10.3.1 Macro Basics

Consider the following simple macros.

#define SQR(x) ((x)*(x))

#define SGN(x) (((x)<0) ? -1 : 1)

#define ABS(x) (((x)<0) ? -(x) : (x))

#define ISDIGIT(x) ((x) >= ’0’ && (x) <= ’9’)

#define NELEMS(array) (sizeof(array) / sizeof(array[0]))

SQRcalculates the square of its argument, SGNcalculates the sign of its argument,ABSconverts its argument to an absolute value, ISDIGIT equals 1 if the argument value is between the character code for 0 and the character code for 9, andNELEMS computes the number of elements in an array.

Macros should be used with care. The preprocessor is a powerful but rather blunt instrument, and it is easy to use macros incorrectly. Macros are subject to three main dangers. The first is that the passed arguments may have surprising precedence after macro expansion. For example, ifSQR were defined as

#define SQR(x) x * x then the following expression

int a = 7;

b = SQR(a+1);

will expand to

b = a+1 * a+1; /* b equals 7 + 1*7 + 1 = 15, not the expected 64 */

For this reason, macro arguments should be heavily parenthesised as with the set of examples above.

The second danger is that arguments with side-effects may be evaluated multiple times after macro expansion. For example,

b = ABS(a++);

will expand to

b = (((a++)<0) ? -(a++) : (a++));

so thatais incremented twice, which is not the expected behaviour. To avoid these sort of problems, it is good practice to never use expressions with side-effects2 as macro arguments. The final danger is that the ability to bypass the C type-checking system is a double-edged sword. It permits greater flexibility, but also prevents the compiler from catching some type-mismatch bugs.

In general, functions are to be preferred over macros as they are safer. However, with a little care, macros can be used without significant trouble, when required.

10.3.2 More Macros

There are many neat and ingenious macros to be found in existing source code, and there is much to be learned from other peoples invention. The following two examples are simple and clever.

#define CLAMP(val,low,high) ((val)<(low) ? (low) : (val) > (high) ? (high) : (val))

#define ROUND(val) ((val)>0 ? (int)((val)+0.5) : -(int)(0.5-(val))) The first,CLAMP, uses two nested?:expressions to bound a valuevalso that if it is less-thanlowit becomes equal tolow, and if it is greater-thanhighit becomes equal tohigh, otherwise it remains unchanged. The second macro, ROUND, rounds a floating-point value to the nearest integer. It performs this operation using the truncation properties of casting adoubleto anint, but contains one clever subtlety. The truncation by casting tointis straightforward if the value is positive, but machine dependent if the value is negative (see Section 2.7). ROUND gets around this problem by subtracting the negative value from 0.5, thus making a positive value, and then negating the answer.

Another clever macro trick is used to make macros behave more like functions. Consider the following macro that swaps two variables (using an additional temporary value).

#define SWAP(x,y,tmp) { tmp=x; x=y; y=tmp; } This operation might be used as in the next example

int a=4, b=-1, temp;

SWAP(a, b, temp);

However, this macro will not behave in a function-like manner if used in anif-statement if (a > b) SWAP(a, b, temp); /* Won’t compile */

else a = b;

as this code will be expanded to incorrect C syntax.

2Side-effects is a term used to describe expressions where the value of some variables are changed as a by-product of the expression evaluation. For example,a++andb *= n, are expressions with side-effects.

if (a > b) { temp=a; a=b; b=temp; }

;

else a = b;

A solution to this problem is to wrap the body of the macro in ado-while statement, which will consume the offending semicolon.

#define SWAP(x,y,tmp) do { tmp=x; x=y; y=tmp; } while (0) An alternative solution is to wrap the macro in anif-elsestatement.

#define SWAP(x,y,tmp) if(1) { tmp=x; x=y; y=tmp; } else

A variant of SWAP does away with defining an explicit temporary variable by simply passing the variable type to the macro.

#define SWAP(x,y,type) do { type tmp=x; x=y; y=tmp; } while (0) This might be used as

SWAP(a, b, double);

Finally, a very trickybitwisetechnique allows us to perform the swap operation without any tempo-rary variable at all. (However, this variant is only valid ifxandyare integer variables of the same type.)

#define SWAP(x,y) do { x^=y; y^=x; x^=y; } while (0)

10.3.3 More Complex Macros

Normally a macro definition extends to the end of the line beginning with the command#define.

However, long macros can be split over several lines by placing a \ at the end of the line to be continued. For example,

#define ERROR(condition, message) \ if (condition) printf(message)

A more interesting example, adapted from [KP99, page 240], performs a timing loop on a section of code using the standard library functionclock(), which returns processor time in milliseconds.3

#define TIMELOOP(CODE) { \

t0 = clock(); \

for (i = 0; i<n; ++i) { CODE; } \ printf("%7d ", clock() - t0); \ }

This macro might be used as follows.

TIMELOOP(y = sin(x));

It is possible to convert a token to a string constant by writing a macro that uses the #symbol in the manner of the following example.

#define PRINT_DEBUG(expr) printf(#expr " = %g\n", expr)

3Measuring the runtime of code is a tricky business as the function in question might have a short execution time compared to the latency of the timing function itself. Thus, reasonable measurements require running the function multiple times and timing the period of the entire loop.

This macro when invoked will print the expression and its result, as the first instance of the expression is converted to a string constant by the preceding#. For example,

PRINT_DEBUG(x/y);

is expanded to

printf("x/y" " = %g\n", x/y);

Another rather obscure preprocessor operator is ##, which provides a way to concatenate two tokens. For example,

#define TEMP(i) temp ## i

might be used to create different temporary variable names TEMP(1) = TEMP(2);

which, after preprocessing, becomes temp1 = temp2;

The preprocessor defines a number of predefined macros. These are __LINE__, __FILE__, __DATE__,__TIME__,__STDC__, and__STDC_VERSION__. Notice that each of these names is prefixed and suffixed by double underscore characters. Determining the meaning of each of these macros is left as an exercise to the reader.

To put the various aspects of this section together, consider the following macro,

#define PRINT_DEBUG(expr, type) \ printf("File: " __FILE__ \

"\nLine: %d\nExpr: " #expr \

" = %" type##TYPE "\n", __LINE__, (expr))

which, given a set of definitions for formatting different types, e.g.,

#define intTYPE "d"

#define doubleTYPE "f"

can be used as

PRINT_DEBUG(x/y, double);

which will print the source-file name, the statement line number, the expression and its value. Indeed a clever and useful debugging macro.