C Language interview questions

C Language quiz questions

  • 1.

    What is the concatenation operator?

    Answer:

    The concatenation operator (##) is used to concatenate (combine) two separate strings into one single string. The concatenation operator is often used in C macros, as the following program demonstrates:

    #include <stdio.h>
    #define SORT(x) sort_function ## x
    void main(void);
    void main(void)
    {
         char* array;
         int   elements, element_size;
         ...
         SORT(3)(array, elements, element_size);
         ...
    }
    

    In the preceding example, the SORT macro uses the concatenation operator to combine the strings sort_function and whatever is passed in the parameter x. This means that the line

    SORT(3)(array, elements, element_size);

    is run through the preprocessor and is translated into the following line:

    sort_function3(array, elements, element_size);

    As you can see, the concatenation operator can come in handy when you do not know what function to call until runtime. Using the concatenation operator, you can dynamically construct the name of the function you want to call, as was done with the SORT macro.

    View
  • 2.

    How many levels deep can include files be nested?

    Answer:

    Even though there is no limit to the number of levels of nested include files you can have, your compiler might run out of stack space while trying to include an inordinately high number of files. This number varies according to your hardware configuration and possibly your compiler.

    In practice, although nesting include files is perfectly legal, you should avoid getting nest-crazy and purposely implementing a large number of include levels. You should create an include level only where it makes sense, such as creating one include file that has an #include statement for each header required by the module you are working with.

    View
  • 3.

    Can include files be nested?

    Answer:

    Yes. Include files can be nested any number of times. As long as you use precautionary measures, you can avoid including the same file twice. In the past, nesting header files was seen as bad programming practice, because it complicates the dependency tracking function of the MAKE program and thus slows down compilation. Many of today's popular compilers make up for this difficulty by implementing a concept called precompiled headers, in which all headers and associated dependencies are stored in a precompiled state.

    Many programmers like to create a custom header file that has #include statements for every header needed for each module. This is perfectly acceptable and can help avoid potential problems relating to #include files, such as accidentally omitting an #include file in a module.

    View
  • 4.

    Can you define which header file to include at compile time?

    Answer:

    Yes. This can be done by using the #if#else, and #endif preprocessor directives. For example, certain compilers use different names for header files. One such case is between Borland C++, which uses the header file alloc.h, and Microsoft C++, which uses the header file malloc.h. Both of these headers serve the same purpose, and each contains roughly the same definitions. If, however, you are writing a program that is to support Borland C++ and Microsoft C++, you must define which header to include at compile time. The following example shows how this can be done:

    #ifdef __BORLANDC__
    #include <alloc.h>
    #else
    #include <malloc.h>
    #endif
    

    When you compile your program with Borland C++, the __BORLANDC__ symbolic name is automatically defined by the compiler. You can use this predefined symbolic name to determine whether your program is being compiled with Borland C++. If it is, you must include the alloc.h file rather than the malloc.h file.

    View
  • 5.

    What is the difference between #include and #include "file" ?

    Answer:

    When writing your C program, you can include files in two ways. The first way is to surround the file you want to include with the angled brackets < and >. This method of inclusion tells the preprocessor to look for the file in the predefined default location. This predefined default location is often an INCLUDE environment variable that denotes the path to your include files. For instance, given the INCLUDE variable

    INCLUDE=C:\COMPILER\INCLUDE;S:\SOURCE\HEADERS;

    using the #include version of file inclusion, the compiler first checks the C:\COMPILER\INCLUDE directory for the specified file. If the file is not found there, the compiler then checks the S:\SOURCE\HEADERSdirectory. If the file is still not found, the preprocessor checks the current  directory.

    The second way to include files is to surround the file you want to include with double quotation marks. This method of inclusion tells the preprocessor to look for the file in the current directory first, then look for it in the predefined locations you have set up. Using the #include "file" version of file inclusion and applying it to the preceding example, the preprocessor first checks the current directory for the specified file. If the file is not found in the current directory, the C:\COMPILER\INCLUDE directory is searched. If the file is still not found, the preprocessor checks the S:\SOURCE\HEADERS directory.

    The #include <file> method of file inclusion is often used to include standard headers such as stdio.hor stdlib.h. This is because these headers are rarely (if ever) modified, and they should always be read from your compiler's standard include file directory.

    The #include "file" method of file inclusion is often used to include nonstandard header files that you have created for use in your program. This is because these headers are often modified in the current directory, and you will want the preprocessor to use your newly modified version of the header rather than the older, unmodified version.

    View
  • 6.

    What is the best way to comment out a section of code that contains comments?

    Answer:

    Most C compilers offer two ways of putting comments in your program. The first method is to use the /* and */ symbols to denote the beginning and end of a comment. Everything from the /* symbol to the */ symbol is considered a comment and is omitted from the compiled version of the program. This method is best for commenting out sections of code that contain many comments. For instance, you can comment out a paragraph containing comments like this:

    /*
    This portion of the program contains
    a comment that is several lines long
    and is not included in the compiled
    version of the program.
    */
    

    The other way to put comments in your program is to use the // symbol. Everything from the // symbol to the end of the current line is omitted from the compiled version of the program. This method is best for one-line comments, because the // symbol must be replicated for each line that you want to add a comment to. The preceding example, which contains four lines of comments, would not be a good candidate for this method of commenting, as demonstrated here:

    // This portion of the program contains
    // a comment that is several lines long
    // and is not included in the compiled
    // version of the program.
    

    You should consider using the /* and */ method of commenting rather than the // method, because the // method of commenting is not ANSI compatible. Many older compilers might not support the // comments.

    View
  • 7.

    Is it better to use a macro or a function?

    Answer:

    The answer depends on the situation you are writing code for. Macros have the distinct advantage of being more efficient (and faster) than functions, because their corresponding code is inserted directly into your source code at the point where the macro is called. There is no overhead involved in using a macro like there is in placing a call to a function. However, macros are generally small and cannot handle large, complex coding constructs. A function is more suited for this type of situation.

    Additionally, macros are expanded inline, which means that the code is replicated for each occurrence of a macro. Your code therefore could be somewhat larger when you use macros than if you were to use functions.

    Thus, the choice between using a macro and using a function is one of deciding between the tradeoff of faster program speed versus smaller program size. Generally, you should use macros to replace small, repeatable code sections, and you should use functions for larger coding tasks that might require several lines of code.

    View
  • 8.

    How are portions of a program disabled in demo versions?

    Answer:

    If you are distributing a demo version of your program, the preprocessor can be used to enable or disable portions of your program. The following portion of code shows how this task is accomplished, using the preprocessor directives #if and #endif:

    int save_document(char* doc_name)
    {
    #if DEMO_VERSION
         printf("Sorry! You can't save documents using the DEMO version of
                 this program!\n");
         return(0);
    #endif
         ...
    }
    

    When you are compiling the demo version of your program, insert the line #define DEMO_VERSION and the preprocessor will include the conditional code that you specified in the save_document() function. This action prevents the users of your demo program from saving their documents.

    As a better alternative, you could define DEMO_VERSION in your compiler options when compiling and avoid having to change the source code for the program.

    This technique can be applied to many different situations. For instance, you might be writing a program that will support several operating systems or operating environments. You can create macros such asWINDOWS_VERUNIX_VER, and DOS_VER that direct the preprocessor as to what code to include in your program depending on what operating system you are compiling for.

    View
  • 9.

    What is the benefit of using an enum rather than a #define constant?

    Answer:

    The use of an enumeration constant (enum) has many advantages over using the traditional symbolic constant style of #define. These advantages include a lower maintenance requirement, improved program readability, and better debugging capability. The first advantage is that enumerated constants are generated automati- cally by the compiler. Conversely, symbolic constants must be manually assigned values by the programmer. For instance, if you had an enumerated constant type for error codes that could occur in your program, your enum definition could look something like this:

    enum Error_Code
    {
         OUT_OF_MEMORY,
         INSUFFICIENT_DISK_SPACE,
         LOGIC_ERROR,
         FILE_NOT_FOUND
    };
    

    In the preceding example, OUT_OF_MEMORY is automatically assigned the value of 0 (zero) by the compiler because it appears first in the definition. The compiler then continues to automatically assign numbers to the enumerated constants, making INSUFFICIENT_DISK_SPACE equal to 1, LOGIC_ERROR equal to 2, and so on.

    If you were to approach the same example by using symbolic constants, your code would look something like this:

    #define OUT_OF_MEMORY 0

    #define INSUFFICIENT_DISK_SPACE 1

    #define LOGIC_ERROR 2

    #define FILE_NOT_FOUND 3

    Each of the two methods arrives at the same result: four constants assigned numeric values to represent error codes. Consider the maintenance required, however, if you were to add two constants to represent the error codes DRIVE_NOT_READY and CORRUPT_FILE. Using the enumeration constant method, you simply would put these two constants anywhere in the enum definition. The compiler would generate two unique values for these constants. Using the symbolic constant method, you would have to manually assign two new numbers to these constants. Additionally, you would want to ensure that the numbers you assign to these constants are unique. Because you don't have to worry about the actual values, defining your constants using the enumerated method is easier than using the symbolic constant method. The enumerated method also helps prevent accidentally reusing the same number for different constants.

    Another advantage of using the enumeration constant method is that your programs are more readable and thus can be understood better by others who might have to update your program later. For instance, consider the following piece of code:

    void copy_file(char* source_file_name, char* dest_file_name)
    {
         ...
         Error_Code err;
         ...
          if (drive_ready() != TRUE)
              err = DRIVE_NOT_READY;
         ...
    }
    

    Looking at this example, you can derive from the definition of the variable err that err should be assigned only numbers of the enumerated type Error_Code. Hence, if another programmer were to modify or add functionality to this program, the programmer would know from the definition of Error_Code what constants are valid for assigning to err.

    Conversely, if the same example were to be applied using the symbolic constant method, the code would look like this:

    void copy_file(char* source_file, char* dest_file)
    {
         ...
         int err;
         ...
         if (drive_ready() != TRUE)
              err = DRIVE_NOT_READY;
         ...
    }
    

    Looking at the preceding example, a programmer modifying or adding functionality to the copy_file()function would not immediately know what values are valid for assigning to the err variable. The programmer would need to search for the #define DRIVE_NOT_READY statement and hope that all relevant constants are defined in the same header file. This could make maintenance more difficult than it needs to be and make your programs harder to understand.

    A third advantage to using enumeration constants is that some symbolic debuggers can print the value of an enumeration constant. Conversely, most symbolic debuggers cannot print the value of a symbolic constant. This can be an enormous help in debugging your program, because if your program is stopped at a line that uses an enum, you can simply inspect that constant and instantly know its value. On the other hand, because most debuggers cannot print #define values, you would most likely have to search for that value by manually looking it up in a header file.

    View
  • 10.

    What is the benefit of using enum to declare a constant?

    Answer:

    Using the enum keyword to define a constant can have several benefits. First, constants declared with enumare automatically generated by the compiler, thereby relieving the programmer of manually assigning unique values to each constant. Also, constants declared with enum tend to be more readable to the programmer, because there is usually an enumerated type identifier associated with the constant's definition.

    Additionally, enumerated constants can usually be inspected during a debugging session. This can be an enormous benefit, especially when the alternative is having to manually look up the constant's value in a header file. Unfortunately, using the enum method of declaring constants takes up slightly more memory space than using the #define method of declaring constants, because a memory location must be set up to store the constant.

    Here is an example of an enumerated constant used for tracking errors in your program:

    enum Error_Code
    {
         OUT_OF_MEMORY,
         INSUFFICIENT_DISK_SPACE,
         LOGIC_ERROR,
         FILE_NOT_FOUND
    };
    View
  • 11.

    What is the benefit of using #define to declare a constant?

    Answer:

    Using the #define method of declaring a constant enables you to declare a constant in one place and use it throughout your program. This helps make your programs more maintainable, because you need to maintain only the #define statement and not several instances of individual constants throughout your program. For instance, if your program used the value of pi (approximately 3.14159) several times, you might want to declare a constant for pi as follows:

    #define PI 3.14159

    This way, if you wanted to expand the precision of pi for more accuracy, you could change it in one place rather than several places. Usually, it is best to put #define statements in an include file so that several modules can use the same constant value.

    Using the #define method of declaring a constant is probably the most familiar way of declaring constants to traditional C programmers. Besides being the most common method of declaring constants, it also takes up the least memory. Constants defined in this manner are simply placed directly into your source code, with no variable space allocated in memory. Unfortunately, this is one reason why most debuggers cannot inspect constants created using the #define method.

    Constants defined with the #define method can also be overridden using the #undef preprocessor directive. This means that if a symbol such as NULL is not defined the way you would like to see it defined, you can remove the previous definition of NULL and instantiate your own custom definition.

    View
  • 12.

    Can a file other than a .h file be included with #include?

    Answer:

    The preprocessor will include whatever file you specify in your #include statement. Therefore, if you have the line

    #include <macros.inc>

    in your program, the file macros.inc will be included in your precompiled program. It is, however, unusual programming practice to put any file that does not have a .h or .hpp extension in an #include statement. You should always put a .h extension on any of your C files you are going to include. This method makes it easier for you and others to identify which files are being used for preprocessing purposes.

    For instance, someone modifying or debugging your program might not know to look at the macros.inc file for macro definitions. That person might try in vain by searching all files with .h extensions and come up empty. If your file had been named macros.h, the search would have included the macros.h file, and the searcher would have been able to see what macros you defined in it.

    View
  • 13.

    How can you avoid including a header more than once?

    Answer:

    One easy technique to avoid multiple inclusions of the same header is to use the #ifndef and #definepreprocessor directives. When you create a header for your program, you can #define a symbolic name that is unique to that header. You can use the conditional preprocessor directive named #ifndef to check whether that symbolic name has already been assigned. If it is assigned, you should not include the header, because it has already been preprocessed. If it is not defined, you should define it to avoid any further inclusions of the header. The following header illustrates this technique:

    #ifndef _FILENAME_H
    #define _FILENAME_H
    #define VER_NUM      "1.00.00"
    #define REL_DATE     "08/01/94"
    #if __WINDOWS__
    #define OS_VER       "WINDOWS"
    #else
    #define OS_VER       "DOS"
    #endif
    #endif
    

    When the preprocessor encounters this header, it first checks to see whether _FILENAME_H has been defined. If it hasn't been defined, the header has not been included yet, and the _FILENAME_H symbolic name is defined. Then, the rest of the header is parsed until the last #endif is encountered, signaling the end of the conditional #ifndef _FILENAME_H statement. Substitute the actual name of the header file for "FILENAME" in the preceding example to make it applicable for your programs.

    View
  • 14.

    What will the preprocessor do for a program?

    Answer:

    The C preprocessor is used to modify your program according to the preprocessor directives in your source code. A preprocessor directive is a statement (such as #define) that gives the preprocessor specific instructions on how to modify your source code. The preprocessor is invoked as the first part of your compiler program's compilation step. It is usually hidden from the programmer because it is run automatically by the compiler.

    The preprocessor reads in all of your include files and the source code you are compiling and creates a preprocessed version of your source code. This preprocessed version has all of its macros and constant symbols replaced by their corresponding code and value assignments. If your source code contains any conditional preprocessor directives (such as #if), the preprocessor evaluates the condition and modifies your source code accordingly.

    Here is an example of a program that uses the preprocessor extensively:

    #include <stdio.h>
    #define TRUE         1
    #define FALSE        (!TRUE)
    #define GREATER(a,b) ((a) > (b) ? (TRUE) : (FALSE))
    #define PIG_LATIN    FALSE
    void main(void);
    void main(void)
    {
         int x, y;
    #if PIG_LATIN
         printf("Easeplay enternay ethay aluevay orfay xnay: ");
         scanf("%d", &x);
         printf("Easeplay enternay ethay aluevay orfay ynay: ");
         scanf("%d", &y);
    #else
         printf("Please enter the value for x: ");
         scanf("%d", &x);
         printf("Please enter the value for y: ");
         scanf("%d", &y);
    #endif
         if (GREATER(x,y) == TRUE)
         {
    #if PIG_LATIN
              printf("xnay islay eatergray anthay ynay!\n");
    #else
              printf("x is greater than y!\n");
    #endif
         }
         else
         {
         #if PIG_LATIN
              printf("xnay islay otnay eatergray anthay ynay!\n");
    #else
              printf("x is not greater than y!\n");
    #endif
         }
    }
    

    This program uses preprocessor directives to define symbolic constants (such as TRUEFALSE, and PIG_LATIN), a macro (such as GREATER(a,b)), and conditional compilation (by using the #if statement). When the preprocessor is invoked on this source code, it reads in the stdio.h file and interprets its preprocessor directives, then it replaces all symbolic constants and macros in your program with the corresponding values and code. Next, it evaluates whether PIG_LATIN is set to TRUE and includes either the pig latin text or the plain English text.

    If PIG_LATIN is set to FALSE, as in the preceding example, a preprocessed version of the source code would look like this:

    /* Here is where all the include files
       would be expanded. */
    void main(void)
    {
         int x, y;
         printf("Please enter the value for x: ");
         scanf("%d", &x);
         printf("Please enter the value for y: ");
         scanf("%d", &y);
         if (((x) > (y) ? (1) : (!1)) == 1)
         {
              printf("x is greater than y!\n");
         }
         else
         {
              printf("x is not greater than y!\n");
         }
    }
    

    This preprocessed version of the source code can then be passed on to the compiler. If you want to see a preprocessed version of a program, most compilers have a command-line option or a standalone preprocessor program to invoke only the preprocessor and save the preprocessed version of your source code to a file. This capability can sometimes be handy in debugging strange errors with macros and other preprocessor directives, because it shows your source code after it has been run through the preprocessor.

    View
  • 15.

    What is a macro, and how do you use it?

    Answer:

    A macro is a preprocessor directive that provides a mechanism for token replacement in your source code. Macros are created by using the #define statement. Here is an example of a macro:

    #define VERSION_STAMP "1.02"

    The macro being defined in this example is commonly referred to as a symbol. The symbol VERSION_STAMPis simply a physical representation of the string "1.02". When the preprocessor is invoked, every occurrence of the VERSION_STAMP symbol is replaced with the literal string "1.02". Here is another example of a macro:

    #define CUBE(x) ((x) * (x) * (x))

    The macro being defined here is named CUBE, and it takes one argument, x. The rest of the code on the line represents the body of the CUBE macro. Thus, the simplistic macro CUBE(x) will represent the more complex expression ((x) * (x) * (x)). When the preprocessor is invoked, every instance of the macro CUBE(x)in  your program is replaced with the code ((x) * (x) * (x)).

    Macros can save you many keystrokes when you are coding your program. They can also make your program much more readable and reliable, because you enter a macro in one place and use it in potentially several places. There is no overhead associated with macros, because the code that the macro represents is expanded in-place, and no jump in your program is invoked. Additionally, the arguments are not type-sensitive, so you don't have to worry about what data type you are passing to the macro.

    Note that there must be no white space between your macro name and the parentheses containing the argument definition. Also, you should enclose the body of the macro in parentheses to avoid possible ambiguity regarding the translation of the macro. For instance, the following example shows the CUBE macro defined incorrectly:

    #define CUBE (x) x * x * x

    You also should be careful with what is passed to a macro. For instance, a very common mistake is to pass an incremented variable to a macro, as in the following example:

    #include <stdio.h>
    #define CUBE(x) (x*x*x)
    void main(void);
    void main(void)
    {
         int x, y;
         x = 5;
         y = CUBE(++x);
         printf("y is %d\n", y);
    }
    

    What will y be equal to? You might be surprised to find out that y is not equal to 125 (the cubed value of 5) and not equal to 336 (6 * 7 * 8), but rather is 512. This is because the variable x is incremented while being passed as a parameter to the macro. Thus, the expanded CUBE macro in the preceding example actually appears as follows:

    y = ((++x) * (++x) * (++x));

    Each time x is referenced, it is incremented, so you wind up with a very different result from what you had intended. Because x is referenced three times and you are using a prefix increment operator, x is actually 8 when the code is expanded. Thus, you wind up with the cubed value of 8 rather than 5. This common mistake is one you should take note of because tracking down such bugs in your software can be a very frustrating experience. I personally have seen this mistake made by people with many years of C programming under their belts. I recommend that you type the example program and see for yourself how surprising the resulting value (512) is.

    Macros can also utilize special operators such as the stringizing operator (#) and the concatenation operator (##). The stringizing operator can be used to convert macro parameters to quoted strings, as in the following example:

    #define DEBUG_VALUE(v) printf(#v " is equal to %d.\n", v)

    In your program, you can check the value of a variable by invoking the DEBUG_VALUE macro: ... int x = 20; DEBUG_VALUE(x); ...

    The preceding code prints "x is equal to 20." on-screen. This example shows that the stringizing operator used with macros can be a very handy debugging tool.

    The concatenation operator (##) is used to concatenate (combine) two separate strings into one single string.

    View
  • 16.

    How are 16- and 32-bit numbers stored?

    Answer:

    A 16-bit number takes two bytes of storage, a most significant byte and a least significant byte. If you write the 16-bit number on paper, you would start with the most significant byte and end with the least significant byte. There is no convention for which order to store them in memory, however.

    Let's call the most significant byte M and the least significant byte L. There are two possible ways to store these bytes in memory. You could store M first, followed by L, or L first, followed by M. Storing byte M first in memory is called "forward" or "big-endian" byte ordering. The term big endian comes from the fact that the "big end" of the number comes first, and it is also a reference to the book Gulliver's Travels, in which the term refers to people who eat their boiled eggs with the big end on top.

    Storing byte L first is called "reverse" or "little-endian" byte ordering. Most machines store data in a big- endian format. Intel CPUs store data in a little-endian format, however, which can be confusing when someone is trying to connect an Intel microprocessor-based machine to anything else.

    A 32-bit number takes four bytes of storage. Let's call them MmMlLm, and Ll in decreasing order of significance. There are 4! (4 factorial, or 24) different ways in which these bytes can be ordered. Over the years, computer designers have used just about all 24 ways. The most popular two ways in use today, however, are (MmMlLmLl), which is big-endian, and (LlLmMlMm), which is little-endian. As with 16-bit numbers, most machines store 32-bit numbers in a big-endian format, but Intel machines store 32-bit numbers in a little-endian format.

    View
  • 17.

    Is it better to bitshift a value than to multiply by 2?

    Answer:

    Any decent optimizing compiler will generate the same code no matter which way you write it. Use whichever form is more readable in the context in which it appears. The following program's assembler code can be viewed with a tool such as CODEVIEW on DOS/Windows or the disassembler (usually called "dis") on UNIX machines:

    Example: Multiplying by 2 and shifting left by 1 are often the same.

    void main()
    {
      unsigned int test_nbr = 300;
      test_nbr *= 2;
      test_nbr = 300;
      test_nbr <<= 1;
    }
    View
  • 18.

    Are bit fields portable?

    Answer:

    Bit fields are not portable. Because bit fields cannot span machine words, and because the number of bits in a machine word is different on different machines, a particular program using bit fields might not even compile on a particular machine.

    Assuming that your program does compile, the order in which bits are assigned to bit fields is not defined. Therefore, different compilers, or even different versions of the same compiler, could produce code that would not work properly on data generated by compiled older code. Stay away from using bit fields, except in cases in which the machine can directly address bits in memory and the compiler can generate code to take advantage of it and the increase in speed to be gained would be essential to the operation of the program.

    View
  • 19.

    What is meant by "bit masking"?

    Answer:

    Bit masking means selecting only certain bits from byte(s) that might have many bits set. To examine some bits of a byte, the byte is bitwise "ANDed" with a mask that is a number consisting of only those bits of interest. For instance, to look at the one's digit (rightmost digit) of the variable flags, you bitwise AND it with a mask of one (the bitwise AND operator in C is &):

    flags & 1;

    To set the bits of interest, the number is bitwise "ORed" with the bit mask (the bitwise OR operator in C is |). For instance, you could set the one's digit of flags like so:

    flags = flags | 1;

    Or, equivalently, you could set it like this:

    flags |= 1;

    To clear the bits of interest, the number is bitwise ANDed with the one's complement of the bit mask. The "one's complement" of a number is the number with all its one bits changed to zeros and all its zero bits changed to ones. The one's complement operator in C is ~. For instance, you could clear the one's digit of flags like so:

    flags = flags & ~1;

    Or, equivalently, you could clear it like this:

    flags &= ~1;

    Sometimes it is easier to use macros to manipulate flag values.

    Example Program : Macros that make manipulating flags easier.

    /* Bit Masking */
    /* Bit masking can be used to switch a character
       between lowercase and uppercase */
    #define BIT_POS(N)            ( 1U << (N) )
    #define SET_FLAG(N, F)        ( (N) |= (F) )
    #define CLR_FLAG(N, F)        ( (N) &= -(F) )
    #define TST_FLAG(N, F)        ( (N) & (F) )
    #define BIT_RANGE(N, M)       ( BIT_POS((M)+1 - (N))-1 << (N) )
    #define BIT_SHIFTL(B, N)      ( (unsigned)(B) << (N) )
    #define BIT_SHIFTR(B, N)      ( (unsigned)(B) >> (N) )
    #define SET_MFLAG(N, F, V)    ( CLR_FLAG(N, F), SET_FLAG(N, V) )
    #define CLR_MFLAG(N, F)       ( (N) &= ~(F) )
    #define GET_MFLAG(N, F)       ( (N) & (F) )
    #include <stdio.h>
    void main()
    {
      unsigned char ascii_char = 'A';        /*  char = 8 bits only */
      int test_nbr = 10;
      printf("Starting character = %c\n", ascii_char);
      /*  The 5th bit position determines if the character is
          uppercase or lowercase.
          5th bit = 0  - Uppercase
          5th bit = 1  - Lowercase      */
      printf("\nTurn 5th bit on = %c\n", SET_FLAG(ascii_char, BIT_POS(5)) );
      printf("Turn 5th bit off = %c\n\n", CLR_FLAG(ascii_char, BIT_POS(5)) );
      printf("Look at shifting bits\n");
      printf("=====================\n");
      printf("Current value = %d\n", test_nbr);
      printf("Shifting one position left = %d\n",
             test_nbr = BIT_SHIFTL(test_nbr, 1) );
      printf("Shifting two positions right = %d\n",
             BIT_SHIFTR(test_nbr, 2) );
    }
    

    BIT_POS(N) takes an integer N and returns a bit mask corresponding to that single bit position (BIT_POS(0)returns a bit mask for the one's digit, BIT_POS(1) returns a bit mask for the two's digit, and so on). So instead of writing

    #define A_FLAG 4096

    #define B_FLAG 8192

    you can write

    #define A_FLAG BIT_POS(12)

    #define B_FLAG BIT_POS(13)

    which is less prone to errors.

    The SET_FLAG(N, F) macro sets the bit at position F of variable N. Its opposite is CLR_FLAG(N, F), which clears the bit at position F of variable N. Finally, TST_FLAG(N, F) can be used to test the value of the bit at position F of variable N, as in

    if (TST_FLAG(flags, A_FLAG))
            /* do something */;
    

    The macro BIT_RANGE(N, M) produces a bit mask corresponding to bit positions N through M, inclusive. With this macro, instead of writing

    #define FIRST_OCTAL_DIGIT 7 /* 111 */

    #define SECOND_OCTAL_DIGIT 56 /* 111000 */

    you can write

    #define FIRST_OCTAL_DIGIT BIT_RANGE(0, 2) /* 111 */

    #define SECOND_OCTAL_DIGIT BIT_RANGE(3, 5) /* 111000 */

    which more clearly indicates which bits are meant.

    The macro BIT_SHIFT(B, N) can be used to shift value B into the proper bit range (starting with bit N). For instance, if you had a flag called C that could take on one of five possible colors, the colors might be defined like this:

    #define C_FLAG          BIT_RANGE(8, 10)      /* 11100000000 */
    /* here are all the values the C flag can take on */
    #define C_BLACK         BIT_SHIFTL(0, 8)       /* 00000000000 */
    #define C_RED           BIT_SHIFTL(1, 8)       /* 00100000000 */
    #define C_GREEN         BIT_SHIFTL(2, 8)       /* 01000000000 */
    #define C_BLUE          BIT_SHIFTL(3, 8)       /* 01100000000 */
    #define C_WHITE         BIT_SHIFTL(4, 8)       /* 10000000000 */
    #define C_ZERO          C_BLACK
    #define C_LARGEST       C_WHITE
    /* A truly paranoid programmer might do this */
    #if C_LARGEST > C_FLAG
            Cause an error message. The flag C_FLAG is not
            big enough to hold all its possible values.
    #endif /* C_LARGEST > C_FLAG */
    

    The macro SET_MFLAG(N, F, V) sets flag F in variable N to the value V. The macro CLR_MFLAG(N, F) is identical to CLR_FLAG(N, F), except the name is changed so that all the operations on multibit flags have a similar naming convention. The macro GET_MFLAG(N, F) gets the value of flag F in variable N, so it can be tested, as in

    if (GET_MFLAG(flags, C_FLAG) == C_BLUE)
            /* do something */;
    View
  • 20.

    What is the most efficient way to store flag values?

    Answer:

    A flag is a value used to make a decision between two or more options in the execution of a program. For instance, the /w flag on the MS-DOS dir command causes the command to display filenames in several columns across the screen instead of displaying them one per line. In which a flag is used to indicate which of two possible types is held in a union. Because a flag has a small number of values (often only two), it is tempting to save memory space by not storing each flag in its own int or char.

    Efficiency in this case is a tradeoff between size and speed. The most memory-space efficient way to store a flag value is as single bits or groups of bits just large enough to hold all the possible values. This is because most computers cannot address individual bits in memory, so the bit or bits of interest must be extracted from the bytes that contain it.

    The most time-efficient way to store flag values is to keep each in its own integer variable. Unfortunately, this method can waste up to 31 bits of a 32-bit variable, which can lead to very inefficient use of memory. If there are only a few flags, it doesn't matter how they are stored. If there are many flags, it might be advantageous to store them packed in an array of characters or integers. They must then be extracted by a process called bit masking, in which unwanted bits are removed from the ones of interest.

    Sometimes it is possible to combine a flag with another value to save space. It might be possible to use high- order bits of integers that have values smaller than what an integer can hold. Another possibility is that some data is always a multiple of 2 or 4, so the low-order bits can be used to store a flag.

    View

© 2017 QuizBucket.org