Ethical Hacking Programming, Blogging, Hosting, All Computer Software, PC Software Download, JAVA in hindi, HTML, PHP, C, C++, Free Learning, Software's Download, Technical Videos, Technical Tricks and Tips, How Make Money

Benefits of Functions in C programming Class 18

4.3 Benefits of Functions


Novice programmers tend to pack all their code into main(), which soon becomes unmanageable. Scalable software design involves breaking a problem into sub-problems, which can each be tackled separately. Functions are the key to enabling such a division and separation of concerns.

Writing programs as a collection of functions has manifold benefits, including the following.

Functions allow a program to be split into a set of subproblems which, in turn, may be further split into smaller subproblems. This divide-and-conquer approach means that small parts of the program can be written, tested, and debugged in isolation without interfering with other parts of the program.

Functions can wrap-up di cult algorithms in a simple and intuitive interface, hiding the im-plementation details, and enabling a higher-level view of the algorithm’s purpose and use.

Functions avoid code duplication. If a particular segment of code is required in several places, a function provides a tidy means for writing the code only once. This is of considerable benefit if the code segment is later altered.2

Consider the following examples. The function names and interfaces give a much higher-level idea of the code’s purpose than does the code itself, and the code is readily reusable.

int toupper (int c)

/* Convert lowercase letters to uppercase, leaving all other characters unchanged. Works correctly 3 * only for character sets with consecutive letters, such as ASCII. */

  {

5if (c >= ’a’ && c <= ’z’)

c += ’A’−’a’;
return c;

   }



int isdigit(int c)

/* Return 1 if c represents an integer character (’0’ to ’9’). This function only works if the character

* codes for 0 to 9 are consecutive, which is the case for ASCII and EBCDIC character sets. */

{

return c >= ’0’ && c <= ’9’;

}



void strcpy (char *s, char *t)

/* Copy character-by-character the string t to the character array s. Copying ceases once the terminating

* ’\0’ has been copied. */

{

int i=0;

while ((s[i] = t[i]) != ’\0’)

++i;

}


double asinh(double x)

/* Compute the inverse hyperbolic sine of an angle x, where x is in radians and -PI <= x <= PI. */

{

return log(x + sqrt(x * x + 1.0));

}

In practice, function interfaces are more stable than code internals (i.e., less subject to change). Thus, the need to find and replace each event in the function call is often less likely than the need to change every event in the Code Segment. The adage “code duplication is an error” is true and well worth bearing in mind.

As a more complex example, consider the function getline() below.3 This function reads a line of characters from standard-input (usually the keyboard) and stores it in a character bu er. Note that this function, in turn, calls the standard library function getchar (), which receives a single character from the standard input. The relative simplicity of the function interface of getline() compared to its definition is immediately apparent.

/* Get a line of data from stdin and store in a character array, s, of size, len. Return the length of the line. 2 * Algorithm from K&R page 69. */

  int getline(char s[ ], int len)

   {

int i=0, c;


/* Loop until: (i) buffer full, (ii) no more input available, or (iii) the end-of-line is reached (marked
* by newline character). */

while (−−len > 0 && (c=getchar()) != EOF && c != ’\n’)
s[i++] = c;
if (c == ’\n’) /* loop terminated by end-of-line, want to keep newline character */

s[i++] = c;
s[i] = ’\0’; /* mark end-of-string */

return i;

}

4.4   Designing For Errors


While writing programs, and especially when designing functions, it is important to make ap-preet error-checking. This section discusses two possible actions for terminal errors, assert() and exit(), and also the use of function return values as a mechanism for reporting non-terminal errors to calling functions.
The standard library macro assert() is used to catch logical errors (i.e., coding bugs, errors that cannot happen in a bug-free program). Situations that “can’t happen” regularly do happen, and assert() is an excellent means for weeding out these often subtle bugs. The form of assert() is as follows,
assert(expression);
where the expression is a conditional test with non-zero being TRUE and zero being FALSE. If the expression is FALSE, then an error has occurred and assert() prints an error message and terminates the program. For example, the expression
assert(idx>=0 && idx<size);
will terminate the program if idx is outside the specified bounds. A common use of assert() is within function definitions to ensure that the calling program uses it correctly. For example,

  int isprime(int val)
/* Brute-force algorithm to check for primeness */ 

 {

int i;
The function getline() is similar to the standard library function gets(), but improves on gets() by including an argument for the maximum-capacity of the character bu er. This oversight in gets() permits a user to overwrite the bu er with input of an over-long line, and this flaw was the loop-hole used by the 1988 Internet Worm to infect thousands of networked machines. (The worm overwrote the stack of a network-querying program called finger, which enabled it to plant back-door code on the remote machine.) For this reason, it is recommended to use the standard function fgets() rather than gets().

Notice the use of the numerical constant 2 in the function isprime(). TThis is an example of a rare case where "magic number" is not bad style. The value 2 is, in fact, not an arbitrary magic number, but is intrinsic to the algorithm, and to use a symbolic constant would actually detract from the code readability.

assert(val >= 2);



for (i = 2; i < val; ++i)

if (val % i == 0)

return 0;

return 1;

}

Another common practice is to place an assert() in the final else of an if-else chain or the default case of a switch when the default condition is not supposed to ever occur.

switch (expression)

{

case label1: statements; break;

case label2: statements; break;

default: assert(0); /* can’t happen */

}

Being a macro, assert() is processed by the C preprocessor, which performs text-replacement on the source code before it is parsed by the compiler.5 If the build is in debug-mode, the preprocessor transforms the assert() into a conditional that, if FALSE, prints a message of the form

Assertion failed: <expression>, file <file name>, line <line number>

and terminates the program. But, if the build is in release-mode (i.e., the non-debug version of the program), then the preprocessor transforms the assert() into nothing—the assertion is ignored. Thus, assertion statements have no e ect on the e ciency on release code.

Note. Assertions greatly assist the code debugging process and incur no runtime penalty on release-version code. Use them liberally.

The standard library function exit() is used to terminate a program either as a normal com-pletion (e.g., in response to a user typing “quit”),

if (user_input == ’q’)

exit(0);

or upon encountering a non-recoverable error (e.g., insu cient memory to complete a dynamic memory request).6

int* mem = (int*) malloc(50 * sizeof(int));

if (mem == NULL)

exit(1);

The form of exit() is

void exit(int status);

where status is the exit-status of the program, which is returned to the calling environment. The value 0 indicates a successful termination and a non-zero value indicates an abnormal termina-tion. (Also, the standard defines two symbolic constants EXIT_SUCCESS and EXIT_FAILURE for this purpose.)

The need to terminate a program in response to a non-recoverable error is not a bug; it can occur in a bug-free program. For example, requesting dynamic memory or opening a file,

The C preprocessor and macros are discussed in detail in Chapter 10.
Operations such as opening files (Chapter 13) and requesting dynamic memory (Chapter 9) deal with resources that may not always be available.

FILE* pfile = fopen("myfile.txt", "r");

if (pfile == NULL)

exit(1);

is dependent on the availability of resources outside of the program control. As such, exit() state-ments will remain in release-version code. Use exit() sparingly—only when the error is terminal, and never inside a function that is designed to be reusable (i.e., functions not tailored to a specific program). Functions designed for reuse should return an error flag to allow the calling function to determine an appropriate action.

Recognising the di erence between situations that require assert() (logical errors caused by coding bugs), and those that require exit() (runtime errors outside the control of the program), is primarily a matter of programming experience.

Note. The function exit() performs various cleanup operations before killing the program (e.g., flushing output streams and calling functions registered with atexit()). A stronger form of termi-nation function is abort(), which kills the program without any cleanup; abort() should be avoided in general.


Function return values are often used to report errors to the calling function. The return value might either be used exclusively as a status value,

int function_returns_status (arguments)

{

statements

if (success) return 0;

return 1;

}

or might return a certain range of values in normal circumstances, and a special value in the case of an error. For example,

int function_returns_value (arguments)

{

int val;

statements

if (error) return -1;

return val; /* normal values are non-negative */

}

The idea of a return value is to inform the calling function that an error has occurred, and the calling function is responsible for deciding what action is appropriate. For example, an appropriate action might be to ignore bad input, or to print a message and continue, or, in the worst case, to terminate the program.

In particular, many standard Library Functions return error codes. It is common practice in toy programs to ignore function return values, but production code should always check and respond suitably. In addition, the standard library defines a global error variable errno, which is used by some standard functions to specify what kind of error has occurred. Standard functions that use errno will typically return a value indicating an error has occurred, and the calling function should check errno to determine the type of error.

Share:

No comments:

Post a Comment

Follow On YouTube