C Language interview questions

C Language quiz questions

  • 1.

    If errno contains a nonzero number, is there an error?

    Answer:

    The global variable errno is used by many standard C library functions to pass back to your program an error code that denotes specifically which error occurred. However, your program should not check the value of errno to determine whether an error occurred.

    Usually, the standard C library function you are calling returns with a return code which denotes that an error has occurred and that the value of errno has been set to a specific error number. If no error has occurred or if you are using a library function that does not reference errno, there is a good chance that errno will contain an erroneous value. For performance enhancement, the errno variable is sometimes not cleared by the functions that use it.

    You should never rely on the value of errno alone; always check the return code from the function you are calling to see whether errno should be referenced. Refer to your compiler's library documentation for references to functions that utilize the errno global variable and for a list of valid values for errno.

    View
  • 2.

    What is the difference between a string and an array?

    Answer:

    An array is an array of anything. A string is a specific kind of an array with a well-known convention to determine its length.

    There are two kinds of programming languages: those in which a string is just an array of characters, and those in which it's a special type. In C, a string is just an array of characters (type char), with one wrinkle: a C string always ends with a NUL character. The "value" of an array is the same as the address of (or a pointer to) the first element; so, frequently, a C string and a pointer to char are used to mean the same thing.

    An array can be any length. If it's passed to a function, there's no way the function can tell how long the array is supposed to be, unless some convention is used. The convention for strings is NUL termination; the last character is an ASCII NUL ('\0') character.

    In C, you can have a literal for an integer, such as the value of 42; for a character, such as the value of '*'; or for a floating-point number, such as the value of 4.2e1 for a float or double.

    There's no such thing as a literal for an array of integers, or an arbitrary array of characters. It would be very hard to write a program without string literals, though, so C provides them. Remember, C strings conventionally end with a NUL character, so C string literals do as well. "six times nine" is 15 characters long (including the NUL terminator), not just the 14 characters you can see.

    There's a little-known, but very useful, rule about string literals. If you have two or more string literals, one after the other, the compiler treats them as if they were one big string literal. There's only one terminatingNUL character. That means that "Hello, " "world" is the same as "Hello, world", and that

    char    message[] =
      "This is an extremely long prompt\n"
      "How long is it?\n"
      "It's so long,\n"
      "It wouldn't fit on one line\n";
    

    When defining a string variable, you need to have either an array that's long enough or a pointer to some area that's long enough. Make sure that you leave room for the NUL terminator. The following example code has a problem:

    char greeting[ 12 ];
    strcpy( greeting, "Hello, world" );    /* trouble */
    

    There's a problem because greeting has room for only 12 characters, and "Hello, world" is 13 characters long (including the terminating NUL character). The NUL character will be copied to someplace beyond the greeting array, probably trashing something else nearby in memory. On the other hand,

    char greeting[ 12 ] = "Hello, world"; /* not a string */

    is OK if you treat greeting as a char array, not a string. Because there wasn't room for the NUL terminator, the NUL is not part of greeting. A better way to do this is to write

    char greeting[] = "Hello, world";

    to make the compiler figure out how much room is needed for everything, including the terminating NUL character.

    String literals are arrays of characters (type char), not arrays of constant characters (type const char). The ANSI C committee could have redefined them to be arrays of const char, but millions of lines of code would have screamed in terror and suddenly not compiled. The compiler won't stop you from trying to modify the contents of a string literal. You shouldn't do it, though. A compiler can choose to put string literals in some part of memory that can't be modified—in ROM, or somewhere the memory mapping registers will forbid writes. Even if string literals are someplace where they could be modified, the compiler can make them shared.

    For example, if you write

    char    *p = "message";
    char    *q = "message";
    p[ 4 ] = '\0';  /* p now points to "mess" */
    

    (and the literals are modifiable), the compiler can take one of two actions. It can create two separate string constants, or it can create just one (that both p and q point to). Depending on what the compiler did, q might still be a message, or it might just be a mess.

    View
  • 3.

    Why can't constant values be used to define an array's initial size?

    Answer:

    There are times when constant values can be used and there are times when they can't. A C program can use what C considers to be constant expressions, but not everything C++ would accept.

    When defining the size of an array, you need to use a constant expression. A constant expression will always have the same value, no matter what happens at runtime, and it's easy for the compiler to figure out what that value is. It might be a simple numeric literal:

    char a[ 512 ];

    Or it might be a "manifest constant" defined by the preprocessor:

    #define MAX     512
    /* ... */
    char    a[ MAX ];
    

    Or it might be a sizeof:

    char a[ sizeof( struct cacheObject ) ];

    Or it might be an expression built up of constant expressions:

    char buf[ sizeof( struct cacheObject ) * MAX ];

    Enumerations are allowed too.

    An initialized const int variable is not a constant expression in C:

    int max = 512; /* not a constant expression in C */

    char buffer[ max ]; /* not valid C */

    Using const ints as array sizes is perfectly legal in C++; it's even recommended. That puts a burden on C++ compilers (to keep track of the values of const int variables) that C compilers don't need to worry about. On the other hand, it frees C++ programs from using the C preprocessor quite so much.

    View
  • 4.

    What is the difference between array_name and &array_name?

    Answer:

    One is a pointer to the first element in the array; the other is a pointer to the array as a whole.

    An array is a type. It has a base type (what it's an array of ), a size (unless it's an "incomplete" array), and a value (the value of the whole array). You can get a pointer to this value:

    char a[ MAX ];      /* array of MAX characters */
    char *p;            /* pointer to one character */
    /* pa is declared below */
    pa = & a;
    p = a;  /* = & a[ 0 ] */
    

    After running that code fragment, you might find that p and pa would be printed as the same value; they both point to the same address. They point to different types of MAX characters.

    The wrong answer is

    char *( ap[ MAX ] );

    which is the same as this:

    char *ap[ MAX ];

    This code reads, "ap is an array of MAX pointers to characters."

    View
  • 5.

    Can you assign a different address to an array tag?

    Answer:

    No, although in one common special case, it looks as if you can. An array tag is not something you can put on the left side of an assignment operator. (It's not an "lvalue," let alone a "modifiable lvalue.") An array is an object; the array tag is a pointer to the first element in that object.

    For an external or static array, the array tag is a constant value known at link time. You can no more change the value of such an array tag than you can change the value of 7.

    Assigning to an array tag would be missing the point. An array tag is not a pointer. A pointer says, "Here's one element; there might be others before or after it." An array tag says, "Here's the first element of an array; there's nothing before it, and you should use an index to find anything after it." If you want a pointer, use a pointer.

    In one special case, it looks as if you can change an array tag:

    void  f( char a[ 12 ] )
    {
            ++a;    /* legal! */
    }
    

    The trick here is that array parameters aren't really arrays. They're really pointers. The preceding example is equivalent to this:

    void  f( char *a )
    {
            ++a;    /* certainly legal */
    }
    

    You can write this function so that the array tag can't be modified. Oddly enough, you need to use pointer syntax:

    void  f( char * const a )
    {
            ++a;    /* illegal */
    }
    

    Here, the parameter is an lvalue, but the const keyword means it's not modifiable.

    View
  • 6.

    Is it better to use a pointer to navigate an array of values, or is it better to use a subscripted array name?

    Answer:

    It's easier for a C compiler to generate good code for pointers than for subscripts.

    Say that you have this:

    /* X is some type */
    X       a[ MAX ];       /* array */
    X       *p;     /* pointer */
    X       x;      /* element */
    int     i;      /* index */
    

    Here's one way to loop through all elements:

    /* version (a) */
    for ( i = 0; i < MAX; ++i )
    {
            x = a[ i ];
            /* do something with x */
    }
    

    On the other hand, you could write the loop this way:

    /* version (b) */
    for ( p = a; p < & a[ MAX ]; ++p )
    {
            x = *p;
            /* do something with x */
    }
    

    What's different between these two versions? The initialization and increment in the loop are the same. The comparison is about the same; more on that in a moment. The difference is between x=a[i] and x=*p. The first has to find the address of a[i]; to do that, it needs to multiply i by the size of an X and add it to the address of the first element of a. The second just has to go indirect on the p pointer. Indirection is fast; multiplication is relatively slow.

    This is "micro efficiency." It might matter, it might not. If you're adding the elements of an array, or simply moving information from one place to another, much of the time in the loop will be spent just using the array index. If you do any I/O, or even call a function, each time through the loop, the relative cost of indexing will be insignificant.

    Some multiplications are less expensive than others. If the size of an X is 1, the multiplication can be optimized away (1 times anything is the original anything). If the size of an X is a power of 2 (and it usually is if X is any of the built-in types), the multiplication can be optimized into a left shift. (It's like multiplying by 10 in base 10.)

    What about computing &a[MAX] every time though the loop? That's part of the comparison in the pointer version. Isn't it as expensive computing a[i] each time? It's not, because &a[MAX] doesn't change during the loop. Any decent compiler will compute that, once, at the beginning of the loop, and use the same value each time. It's as if you had written this:

    /* how the compiler implements version (b) */
    X       *temp = & a[ MAX ];     /* optimization */
    for ( p = a; p < temp; ++p )
    {
            x = *p;
            /* do something with x */
    }
    

    This works only if the compiler can tell that a and MAX can't change in the middle of the loop. There are two other versions; both count down rather than up. That's no help for a task such as printing the elements of an array in order. It's fine for adding the values or something similar. The index version presumes that it's cheaper to compare a value with zero than to compare it with some arbitrary value:

    /* version (c) */
    for ( i = MAX - 1; i >= 0; --i )
    {
            x = a[ i ];
            /* do something with x */
    }
    

    The pointer version makes the comparison simpler:

    /* version (d) */
    for ( p = & a[ MAX - 1 ]; p >= a; --p )
    {
            x = *p;
            /* do something with x */
    }
    

    Code similar to that in version (d) is common, but not necessarily right. The loop ends only when p is less than a. That might not be possible.

    The common wisdom would finish by saying, "Any decent optimizing compiler would generate the same code for all four versions." Unfortunately, there seems to be a lack of decent optimizing compilers in the world. A test program (in which the size of an X was not a power of 2 and in which the "do something" was trivial) was built with four very different compilers. Version (b) always ran much faster than version (a), sometimes twice as fast. Using pointers rather than indices made a big difference. (Clearly, all four compilers optimize &a[MAX] out of the loop.)

    How about counting down rather than counting up? With two compilers, versions (c) and (d) were about the same as version (a); version (b) was the clear winner. (Maybe the comparison is cheaper, but decrementing is slower than incrementing?) With the other two compilers, version (c) was about the same as version (a) (indices are slow), but version (d) was slightly faster than version (b).

    So if you want to write portable efficient code to navigate an array of values, using a pointer is faster than using subscripts. Use version (b); version (d) might not work, and even if it does, it might be compiled into slower code.

    Most of the time, though, this is micro-optimizing. The "do something" in the loop is where most of the time is spent, usually. Too many C programmers are like half-sloppy carpenters; they sweep up the sawdust but leave a bunch of two-by-fours lying around.

    View
  • 7.

    Can the sizeof operator be used to tell the size of an array passed to a function?

    Answer:

    No. There's no way to tell, at runtime, how many elements are in an array parameter just by looking at the array parameter itself. Remember, passing an array to a function is exactly the same as passing a pointer to the first element. This is a Good Thing. It means that passing pointers and arrays to C functions is very efficient.

    It also means that the programmer must use some mechanism to tell how big such an array is. There are two common ways to do that. The first method is to pass a count along with the array. This is what memcpy() does, for example:

    char    source[ MAX ], dest[ MAX ];
    /* ... */
    memcpy( dest, source, MAX );
    

    The second method is to have some convention about when the array ends. For example, a C "string" is just a pointer to the first character; the string is terminated by an ASCII NUL ('\0') character. This is also commonly done when you have an array of pointers; the last is the null pointer. Consider the following function, which takes an array of char*s. The last char* in the array is NULL; that's how the function knows when to stop.

    void printMany( char *strings[] )
    {
            int     i;
            i = 0;
            while ( strings[ i ] != NULL )
            {
                 puts( strings[ i ] );
                 ++i;
            }
    }
    

    Most C programmers would write this code a little more cryptically:

    void  printMany( char *strings[] )
    {
            while ( *strings )
            {
                    puts( *strings++ );
            }
    }
    

    C programmers often use pointers rather than indices. You can't change the value of an array tag, but because strings is an array parameter, it's really the same as a pointer. That's why you can increment strings. Also,

    while ( *strings )

    means the same thing as

    while ( *strings != NULL )

    and the increment can be moved up into the call to puts().

    If you document a function (if you write comments at the beginning, or if you write a "manual page" or a design document), it's important to describe how the function "knows" the size of the arrays passed to it. This description can be something simple, such as "null terminated," or "elephants has numElephants elements." (Or "arr should have 13 elements," if your code is written that way. Using hard coded numbers such as 13 or 64 or 1024 is not a great way to write C code, though.)

    View
  • 8.

    Is it valid to address one element beyond the end of an array?

    Answer:

    It's valid to address it, but not to see what's there. (The really short answer is, "Yes, so don't worry about it.") With most compilers, if you say

    int i, a[MAX], j;

    then either i or j is at the part of memory just after the last element of the array. The way to see whether i or j follows the array is to compare their addresses with that of the element following the array. The way to say this in C is that either

    & i == & a[ MAX ]

    is true or

    & a[ MAX ] == & j

    is true. This isn't guaranteed; it's just the way it usually works. The point is, if you store something in a[MAX], you'll usually clobber something outside the a array. Even looking at the value of a[MAX] is technically against the rules, although it's not usually a problem. Why would you ever want to say &a[MAX]? There's a common idiom of going through every member of a loop using a pointer. Instead of

    for ( i = 0; i < MAX; ++i )
    {
            /* do something */;
    }
    

    C programmers often write this:

    for ( p = a; p < & a[ MAX ]; ++p )
    {
            /* do something */;
    }
    

    The kind of loop shown here is so common in existing C code that the C standard says it must work.

    View
  • 9.

    Do array subscripts always start with zero?

    Answer:

    Yes. If you have an array a[MAX] (in which MAX is some value known at compile time), the first element isa[0], and the last element is a[MAX-1]. This arrangement is different from what you would find in some other languages. In some languages, such as some versions of BASIC, the elements would be a[1] througha[MAX], and in other languages, such as Pascal, you can have it either way.

    This variance can lead to some confusion. The "first element" in non-technical terms is the "zero'th" element according to its array index. If you're using spoken words, use "first" as the opposite of "last." If that's not precise enough, use pseudo-C. You might say, "The elements a sub one through a sub eight," or, "The second through ninth elements of a."

    There's something you can do to try to fake array subscripts that start with one. Don't do it. The technique is described here only so that you'll know why not to use it.

    Because pointers and arrays are almost identical, you might consider creating a pointer that would refer to the same elements as an array but would use indices that start with one. For example:

    /* don't do this!!! */
    int     a0[ MAX ];
    int     *a1 = a0 - 1;   /* & a[ -1 ] */
    

    Thus, the first element of a0 (if this worked, which it might not) would be the same as a1[1]. The last element of a0, a0[MAX-1], would be the same as a1[MAX]. There are two reasons why you shouldn't do this.

    The first reason is that it might not work. According to the ANSI/ISO standard, it's undefined (which is a Bad Thing). The problem is that &a[-1] might not be a valid address; Your program might work all the time with some compilers, and some of the time with all compilers. Is that good enough?

    The second reason not to do this is that it's not C-like. Part of learning C is to learn how array indices work. Part of reading (and maintaining) someone else's C code is being able to recognize common C idioms. If you do weird stuff like this, it'll be harder for people to understand your code. (It'll be harder for you to understand your own code, six months later.)

    View
  • 10.

    How can you check to see whether a symbol is defined?

    Answer:

    You can use the #ifdef and #ifndef preprocessor directives to check whether a symbol has been defined (#ifdef) or whether it has not been defined (#ifndef). Many programmers like to ensure that their own version of NULL is defined, not someone else's. This task can be accomplished as shown here:

    #ifdef NULL
    #undef NULL
    #endif
    #define NULL (void*) 0
    

    The first line, #ifdef NULL, checks to see whether the NULL symbol has been defined. If so, it is undefined using #undef NULL and the new definition of NULL is defined.

    To check whether a symbol has not been defined yet, you would use the #ifndef preprocessor directive.

    View
  • 11.

    How do you override a defined macro?

    Answer:

    You can use the #undef preprocessor directive to undefine (override) a previously defined macro. Many programmers like to ensure that their applications are using their own terms when defining symbols such as TRUE and FALSE. Your program can check to see whether these symbols have been defined already, and if they have, you can override them with your own definitions of TRUE and FALSE. The following portion of code shows how this task can be accomplished:

    ...
    #ifdef TRUE             /* Check to see if TRUE has been defined yet */
    #undef TRUE             /* If so, undefine it */
    #endif
    #define TRUE 1          /* Define TRUE the way we want it defined */
    #ifdef FALSE            /* Check to see if FALSE has been defined yet */
    #undef FALSE            /* If so, undefine it */
    #endif
    #define FALSE !TRUE     /* Define FALSE the way we want it defined */
    ...
    

    In the preceding example, the symbols TRUE and FALSE are checked to see whether they have been defined yet. If so, they are undefined, or overridden, using the #undef preprocessor directive, and they are redefined in the desired manner. If you were to eliminate the #undef statements in the preceding example, the compiler would warn you that you have multiple definitions of the same symbol. By using this technique, you can avoid this warning and ensure that your programs are using valid symbol definitions.

    View
  • 12.

    How can you be sure that a program follows the ANSI C standard?

    Answer:

    The ANSI C standard provides a predefined symbol named __STDC__ that is set to 1 when the compiler is enforcing strict ANSI standard conformance. If you want your programs to be 100 percent ANSI conformant, you should ensure that the __STDC__ symbol is defined. If the program is being compiled with non-ANSI options, the __STDC__ symbol is undefined. The following code segment shows how this symbol can be checked:

    
    #ifdef __STDC__
         printf("Congratulations! You are conforming perfectly to the ANSI
                 standards!\n");
    #else
         printf("Shame on you, you nonconformist anti-ANSI rabble-rousing programmer!\n");
    #endif
    View
  • 13.

    What are the __DATE__ and __TIME__ preprocessor commands?

    Answer:

    The __DATE__ macro is used to insert the current compilation date in the form "mm dd yyyy" into your program. Similarly, the __TIME__ macro is used to insert the current compilation time in the form "hh:mm:ss" into your program. This date-and-time-stamp feature should not be confused with the current system date and time. Rather, these two macros enable you to keep track of the date and time your program was last compiled. This feature can come in very handy when you are trying to track different versions of your program. For instance, many programmers like to put a function in their programs that gives compilation information as to when the current module was compiled. This task can be performed as shown here:

    #include <stdio.h>
    void main(void);
    void print_version_info(void);
    void main(void)
    {
    print_version_info();
    }
    void print_version_info(void)
    {
         printf("Date Compiled: %s\n", __DATE__);
         printf("Time Compiled: %s\n", __TIME__);
    }
    

    In this example, the function print_version_info() is used to show the date and time stamp of the last time this module was compiled.

    View
  • 14.

    What is #line used for?

    Answer:

    The #line preprocessor directive is used to reset the values of the __LINE__ and __FILE__ symbols, respectively. This directive is commonly used in fourth-generation languages that generate C language source files. For instance, if you are using a fourth-generation language named "X," the 4GL compiler will generate C source code routines for compilation based on your 4GL source code. If errors are present in your 4GL code, they can be mapped back to your 4GL source code by using the #line directive. The 4GL code generator simply inserts a line like this into the generated C source:

    #line 752, "XSOURCE.X"
    void generated_code(void)
    {
    ...
    }
    

    Now, if an error is detected anywhere in the generated_code() function, it can be mapped back to the original 4GL source file named XSOURCE.X. This way, the 4GL compiler can report the 4GL source code line that has the error in it.

    When the #line directive is used, the __LINE__ symbol is reset to the first argument after the #linekeyword  (in the preceding example, 752), and the __FILE__ symbol is reset to the second argument after the #line keyword (in the preceding example, "XSOURCE.X"). All references hereafter to the __LINE__ and __FILE__ symbols will reflect the reset values and not the original values of __LINE__ and __FILE__.

    View
  • 15.

    What is a pragma?

    Answer:

    The #pragma preprocessor directive allows each compiler to implement compiler-specific features that can be turned on and off with the #pragma statement. For instance, your compiler might support a feature called loop optimization. This feature can be invoked as a command-line option or as a #pragma directive. To implement this option using the #pragma directive, you would put the following line into your code:

    #pragma loop_opt(on)

    Conversely, you can turn off loop optimization by inserting the following line into your code:

    #pragma loop_opt(off)

    Sometimes you might have a certain function that causes your compiler to produce a warning such as Parameter xxx is never used in function yyy or some other warning that you are well aware of but choose to ignore. You can temporarily disable this warning message on some compilers by using a #pragma directive to turn off the warning message before the function and use another #pragma directive to turn it back on after the function. For instance, consider the following example, in which the function named insert_record() generates a warning message that has the unique ID of 100. You can temporarily disable this warning as shown here:

    #pragma warn -100    /* Turn off the warning message for warning #100 */
    int insert_record(REC* r)   /* Body of the function insert_record() */
    {
        /* insert_rec() function statements go here... */
    }
    #pragma warn +100 /* Turn the warning message for warning #100 back on */
    

    Check your compiler's documentation for a list of #pragma directives. As stated earlier, each compiler's implementation of this feature is different, and what works on one compiler almost certainly won't work on another. Nevertheless, the #pragma directives can come in very handy when you're turning on and off some of your compiler's favorite (or most annoying) features.

    View
  • 16.

    How can you tell whether a program was compiled using C versus C++?

    Answer:

    The ANSI standard for the C language defines a symbol named __cplusplus that is defined only when you are compiling a C++ program. If you are compiling a C program, the __cplusplus symbol is undefined. Therefore, you can check to see whether the C++ compiler has been invoked with the following method:

    #ifdef __cplusplus                      /* Is __cplusplus defined? */
    #define USING_C FALSE                   /* Yes, we are not using C */
    #else
    #define USING_C TRUE                    /* No, we are using C */
    #endif
    

    When the preprocessor is invoked, it sets USING_C to FALSE if the __cplusplus symbol is defined. Otherwise, if __cplusplus is undefined, it sets USING_C to TRUE. Later in your program, you can check the value of the USING_C constant to determine whether the C++ compiler is being used.

    View
  • 17.

    How can a program be made to print the name of a source file where an error occurs?

    Answer:

    The ANSI C standard includes a predefined macro named __FILE__ that can be used to insert the current source code filename in your program. This macro, like the __LINE__ macro, can be very valuable when it comes to debugging your program and checking for logic errors. For instance, the following code, includes the filename as well as the line number when logic errors are trapped:

    int print_document(char* doc_name, int destination)
    {
         switch (destination)
         {
              case TO_FILE:
                   print_to_file(doc_name);
                   break;
              case TO_SCREEN:
                   print_preview(doc_name);
                   break;
              case TO_PRINTER:
                   print_to_printer(doc_name);
                   break;
              default:
                   printf("Logic error on line number %d in the file %s!\n",
                             __LINE__, __FILE__);
                   exit(1);
         }
    }
    

    Now, any erroneous values for the destination parameter can be trapped, and the offending source file and line number can be printed.

    View
  • 18.

    How can a program be made to print the line number where an error occurs?

    Answer:

    The ANSI C standard includes a predefined macro named __LINE__ that can be used to insert the current source code line number in your program. This can be a very valuable macro when it comes to debugging your program and checking for logic errors. For instance, consider the following portion of code:

    int print_document(char* doc_name, int destination)
    {
         switch (destination)
         {
              case TO_FILE:
                   print_to_file(doc_name);
                   break;
              case TO_SCREEN:
                   print_preview(doc_name);
                   break;
              case TO_PRINTER:
                   print_to_printer(doc_name);
                   break;
              default:
                   printf("Logic error on line number %d!\n", __LINE__);
                   exit(1);
         }
    }
    

    If the function named print_document() is passed an erroneous argument for the destination parameter (something other than TO_FILETO_SCREEN, and TO_PRINTER), the default case in the switch statement traps this logic error and prints the line number in which it occurred. This capability can be a tremendous help when you are trying to debug your program and track down what could be a very bad logic error.

    View
  • 19.

    What are the standard predefined macros?

    Answer:

    The ANSI C standard defines six predefined macros for use in the C language:

    Macro Name   Purpose
    __LINE__ - Inserts the current source code line number in your code.
    __FILE__ - Inserts the current source code filename in your code.
    __DATE__ - Inserts the current date of compilation in your code.
    __TIME__ - Inserts the current time of compilation in your code.
    __STDC__ - Is set to 1 if you are enforcing strict ANSI C conformity.
    __cplusplus - Is defined if you are compiling a C++ program.
    View
  • 20.

    How can type-insensitive macros be created?

    Answer:

    A type-insensitive macro is a macro that performs the same basic operation on different data types. This task can be accomplished by using the concatenation operator to create a call to a type-sensitive function based on the parameter passed to the macro. The following program provides an example:

    #include <stdio.h>
    #define SORT(data_type) sort_ ## data_type
    void sort_int(int** i);
    void sort_long(long** l);
    void sort_float(float** f);
    void sort_string(char** s);
    void main(void);
    void main(void)
    {
         int** ip;
         long** lp;
         float** fp;
         char** cp;
         ...
         sort(int)(ip);
         sort(long)(lp);
         sort(float)(fp);
         sort(char)(cp);
         ...
    }
    

    This program contains four functions to sort four different data types: intlongfloat, and string (notice that only the function prototypes are included for brevity). A macro named SORT was created to take the data type passed to the macro and combine it with the sort_ string to form a valid function call that is appropriate for the data type being sorted. Thus, the string

    sort(int)(ip);

    translates into

    sort_int(ip);

    after being run through the preprocessor.

    View

© 2017 QuizBucket.org