C Language interview questions

C Language quiz questions

  • 1.

    How do you print an address?

    Answer:

    The safest way is to use printf() (or fprintf() or sprintf()) with the %P specification. That prints a void pointer (void*). Different compilers might print a pointer with different formats. Your compiler will pick a format that's right for your environment.

    If you have some other kind of pointer (not a void*) and you want to be very safe, cast the pointer to a void*:

    printf( "%P\n", (void*) buffer );

    There's no guarantee any integer type is big enough to store a pointer. With most compilers, an unsigned long is big enough. The second safest way to print an address (the value of a pointer) is to cast it to an unsigned long, then print that.

    View
  • 2.

    Can math operations be performed on a void pointer?

    Answer:

    No. Pointer addition and subtraction are based on advancing the pointer by a number of elements. By definition, if you have a void pointer, you don't know what it's pointing to, so you don't know the size of what it's pointing to.

    If you want pointer arithmetic to work on raw addresses, use character pointers.

    View
  • 3.

    How does free() know how much memory to release?

    Answer:

    There's no standard way. It can vary from compiler to compiler, even from version to version of the same compiler. free()malloc()calloc(), and realloc() are functions; as long as they all work the same way, they can work any way that works.

    Most implementations take advantage of the same trick, though. When malloc() (or one of the other allocation functions) allocates a block of memory, it grabs a little more than it was asked to grab. malloc()doesn't return the address of the beginning of this block. Instead, it returns a pointer a little bit after that.

    At the very beginning of the block, before the address returned, malloc() stores some information, such as how big the block is. (If this information gets overwritten, you'll have wild pointer problems when you free the memory.)

    There's no guarantee free() works this way. It could use a table of allocated addresses and their lengths. It could store the data at the end of the block (beyond the length requested by the call to malloc()). It could store a pointer rather than a count.

    View
  • 4.

    How can you determine the size of an allocated portion of memory?

    Answer:

    You can't, really. free() can, but there's no way for your program to know the trick free() uses.

    View
  • 5.

    What is a "null pointer assignment" error? What are bus errors, memory faults, and core dumps?

    Answer:

    These are all serious errors, symptoms of a wild pointer or subscript.

    Null pointer assignment is a message you might get when an MS-DOS program finishes executing. Some such programs can arrange for a small amount of memory to be available "where the NULL pointer points to" (so to speak). If the program tries to write to that area, it will overwrite the data put there by the compiler. When the program is done, code generated by the compiler examines that area. If that data has been changed, the compiler-generated code complains with null pointer assignment.

    This message carries only enough information to get you worried. There's no way to tell, just from a nullpointer assignment message, what part of your program is responsible for the error. Some debuggers, and some compilers, can give you more help in finding the problem.

    Bus error: core dumped and Memory fault: core dumped are messages you might see from a program running under UNIX. They're more programmer friendly. Both mean that a pointer or an array subscript was wildly out of bounds. You can get these messages on a read or on a write. They aren't restricted to null pointer problems.

    The core dumped part of the message is telling you about a file, called core, that has just been written in your current directory. This is a dump of everything on the stack and in the heap at the time the program was running. With the help of a debugger, you can use the core dump to find where the bad pointer was used.

    That might not tell you why the pointer was bad, but it's a step in the right direction. If you don't have write permission in the current directory, you won't get a core file, or the core dumped message.

    The same tools that help find memory allocation bugs can help find some wild pointers and subscripts, sometimes. The best such tools can find almost all occurrences of this kind of problem.

    View
  • 6.

    What is the difference between NULL and NUL?

    Answer:

    NULL is a macro defined in <stddef.h> for the null pointer.

    NUL is the name of the first character in the ASCII character set. It corresponds to a zero value. There's no standard macro NUL in C, but some people like to define it.

    NULL can be defined as ((void*)0)NUL as '\0'. Both can also be defined simply as 0. If they're defined that way, they can be used interchangeably. That's a bad way to write C code. One is meant to be used as a pointer; the other, as a character. If you write your code so that the difference is obvious, the next person who has to read and change your code will have an easier job. If you write obscurely, the next person might have problems.

    View
  • 7.

    What happens if you free a pointer twice?

    Answer:

    If you free a pointer, use it to allocate memory again, and free it again, of course it's safe

    If you free a pointer, the memory you freed might be reallocated. If that happens, you might get that pointer back. In this case, freeing the pointer twice is OK, but only because you've been lucky. The following example is silly, but safe:

    #include <stdlib.h>
    int main(int argc, char** argv)
    {
            char** new_argv1;
            char** new_argv2;
            new_argv1 = calloc(argc+1, sizeof(char*));
            free(new_argv1);    /* freed once */
            new_argv2 = (char**) calloc(argc+1, sizeof(char*));
            if (new_argv1 == new_argv2) 
            {
                    /* new_argv1 accidentally points to freeable memory */
                    free(new_argv1);    /* freed twice */
            } 
            else 
            {
                    free(new_argv2);
            }
            new_argv1 = calloc(argc+1, sizeof(char*));
            free(new_argv1);    /* freed once again */
            return 0;
    }
    

    In the preceding program, new_argv1 is pointed to a chunk of memory big enough to copy the argv array, which is immediately freed. Then a chunk the same size is allocated, and its address is assigned to new_argv2. Because the first chunk was available again, calloc might have returned it again; in that case, new_argv1 and new_argv2 have the same value, and it doesn't matter which variable you use. (Remember, it's the pointed- to memory that's freed, not the pointer variable.) new_argv1 is pointed to allocated memory again, which is again freed. You can free a pointer as many times as you want; it's the memory you have to be careful about.

    What if you free allocated memory, don't get it allocated back to you, and then free it again? Something like this:

    void caller( ... )
    {
            void *p;
            /* ... */
            callee( p );
            free( p );
    }
    void callee( void* p )
    {
            /* ... */
            free( p );
            return;
    }
    

    In this example, the caller() function is passing p to the callee() function and then freeing p. Unfortunately, callee() is also freeing p. Thus, the memory that p points to is being freed twice. The ANSI/ ISO C standard says this is undefined. Anything can happen. Usually, something very bad happens.

    The memory allocation and deallocation functions could be written to keep track of what has been used and what has been freed. Typically, they aren't. If you free() a pointer, the pointed-to memory is assumed to have been allocated by malloc() or calloc() but not deallocated since then. free() calculates how big that chunk of memory was and updates the data structures in the memory "arena." Even if the memory has been freed already, free() will assume that it wasn't, and it will blindly update the arena. This action is much faster than it would have been if free() had checked to see whether the pointer was OK to deallocate.

    If something doesn't work right, your program is now in trouble. When free() updates the arena, it will probably write some information in a wrong place. You now have the fun of dealing with a wild pointer;

    View
  • 8.

    What is the heap?

    Answer:

    The heap is where malloc()calloc(), and realloc() get memory.

    Getting memory from the heap is much slower than getting it from the stack. On the other hand, the heap is much more flexible than the stack. Memory can be allocated at any time and deallocated in any order. Such memory isn't deallocated automatically; you have to call free().

    Recursive data structures are almost always implemented with memory from the heap. Strings often come from there too, especially strings that could be very long at runtime.

    If you can keep data in a local variable (and allocate it from the stack), your code will run faster than if you put the data on the heap. Sometimes you can use a better algorithm if you use the heap—faster, or more robust, or more flexible. It's a tradeoff.

    If memory is allocated from the heap, it's available until the program ends. That's great if you remember to deallocate it when you're done. If you forget, it's a problem. A "memory leak" is some allocated memory that's no longer needed but isn't deallocated. If you have a memory leak inside a loop, you can use up all the memory on the heap and not be able to get any more. (When that happens, the allocation functions return a null pointer.) In some environments, if a program doesn't deallocate everything it allocated, memory stays unavailable even after the program ends.

    Some programming languages don't make you deallocate memory from the heap. Instead, such memory is "garbage collected" automatically. This maneuver leads to some very serious performance issues. It's also a lot harder to implement. That's an issue for the people who develop compilers, not the people who buy them. (Except that software that's harder to implement often costs more.) There are some garbage collection libraries for C, but they're at the bleeding edge of the state of the art.

    View
  • 9.

    What is the stack?

    Answer:

    A "stack trace" is a list of which functions have been called, based on this information. When you start using a debugger, one of the first things you should learn is how to get a stack trace.

    The stack is very inflexible about allocating memory; everything must be deallocated in exactly the reverse order it was allocated in. For implementing function calls, that is all that's needed. Allocating memory off the stack is extremely efficient. One of the reasons C compilers generate such good code is their heavy use of a simple stack.

    There used to be a C function that any programmer could use for allocating memory off the stack. The memory was automatically deallocated when the calling function returned. This was a dangerous function to call; it's not available anymore.

    View
  • 10.

    When should a far pointer be used?

    Answer:

    Sometimes you can get away with using a small memory model in most of a given program. There might be just a few things that don't fit in your small data and code segments.

    When that happens, you can use explicit far pointers and function declarations to get at the rest of memory. A far function can be outside the 64KB segment most functions are shoehorned into for a small-code model. (Often, libraries are declared explicitly far, so they'll work no matter what code model the program uses.)

    far pointer can refer to information outside the 64KB data segment. Typically, such pointers are used withfarmalloc() and such, to manage a heap separate from where all the rest of the data lives.

    If you use a small-data, large-code model, you should explicitly make your function pointers far.

    View
  • 11.

    What is the difference between far and near?

    Answer:

    Compilers for PC compatibles use two types of pointers.

    near pointers are 16 bits long and can address a 64KB range. far pointers are 32 bits long and can address a 1MB range.

    near pointers operate within a 64KB segment. There's one segment for function addresses and one segment for data.

    far pointers have a 16-bit base (the segment address) and a 16-bit offset. The base is multiplied by 16, so a far pointer is effectively 20 bits long. For example, if a far pointer had a segment of 0x7000 and an offset of 0x1224, the pointer would refer to address 0x71224. A far pointer with a segment of 0x7122 and an offset of 0x0004 would refer to the same address.

    Before you compile your code, you must tell the compiler which memory model to use. If you use a small- code memory model, near pointers are used by default for function addresses. That means that all the functions need to fit in one 64KB segment. With a large-code model, the default is to use far function addresses. You'll get near pointers with a small data model, and far pointers with a large data model. These are just the defaults; you can declare variables and functions as explicitly near or far.

    far pointers are a little slower. Whenever one is used, the code or data segment register needs to be swapped out. far pointers also have odd semantics for arithmetic and comparison. For example, the two far pointers in the preceding example point to the same address, but they would compare as different! If your program fits in a small-data, small-code memory model, your life will be easier. If it doesn't, there's not much you can do.

    If it sounds confusing, it is. There are some additional, compiler-specific wrinkles. Check your compiler manuals for details.

    View
  • 12.

    How do you declare an array that will hold more than 64KB of data?

    Answer:

    The coward's answer is, you can't, portably. The ANSI/ISO C standard requires compilers to handle only single objects as large as (32KB - 1) bytes long.

    Why is 64KB magic? It's the biggest number that needs more than 16 bits to represent it.

    For some environments, to get an array that big, you just declare it. It works, no trouble. For others, you can't declare such an array, but you can allocate one off the heap, just by calling malloc() or calloc().

    On a PC compatible, the same limitations apply, and more. You need to use at least a large data model. You might also need to call "far" variants of malloc() or calloc(). For example, with Borland C and C++ compilers, you could write

    far char *buffer = farmalloc(70000L);

    Or with Microsoft C and C++ compilers, you could write

    far char *buffer = fmalloc(70000L);

    to allocate 70,000 bytes of memory into a buffer. (The L in 70000L forces a long constant. An int constant might be only 15 bits long plus a sign bit, not big enough to store the value 70,000.)

    View
  • 13.

    Is it better to use malloc() or calloc()?

    Answer:

    Both the malloc() and the calloc() functions are used to allocate dynamic memory. Each operates slightly different from the other. malloc() takes a size and returns a pointer to a chunk of memory at least that big:

    void *malloc( size_t size );

    calloc() takes a number of elements, and the size of each, and returns a pointer to a chunk of memory at least big enough to hold them all:

    void *calloc( size_t numElements, size_t sizeOfElement );

    There's one major difference and one minor difference between the two functions. The major difference is that malloc() doesn't initialize the allocated memory. The first time malloc() gives you a particular chunk of memory, the memory might be full of zeros. If memory has been allocated, freed, and reallocated, it probably has whatever junk was left in it. That means, unfortunately, that a program might run in simple cases (when memory is never reallocated) but break when used harder (and when memory is reused).

    calloc() fills the allocated memory with all zero bits. That means that anything there you're going to use as a char or an int of any length, signed or unsigned, is guaranteed to be zero. Anything you're going to use as a pointer is set to all zero bits. That's usually a null pointer, but it's not guaranteed.

    Anything you're going to use as a float or double is set to all zero bits; that's a floating-point zero on some types of machines, but not on all.

    The minor difference between the two is that calloc() returns an array of objects; malloc() returns one object. Some people use calloc() to make clear that they want an array. Other than initialization, most C programmers don't distinguish between

    calloc( numElements, sizeOfElement)

    and

    malloc( numElements * sizeOfElement)

    There's a nit, though. malloc() doesn't give you a pointer to an array. In theory (according to the ANSI C standard), pointer arithmetic works only within a single array. In practice, if any C compiler or interpreter were to enforce that theory, lots of existing C code would break. (There wouldn't be much use for realloc(), either, which also doesn't guarantee a pointer to an array.)

    Don't worry about the array-ness of calloc(). If you want initialization to zeros, use calloc(); if not, use malloc().

    View
  • 14.

    Can the size of an array be declared at runtime?

    Answer:

    No. In an array declaration, the size must be known at compile time. You can't specify a size that's known only at runtime. For example, if i is a variable, you can't write code like this:

    char array[i]; /* not valid C */

    Some languages provide this latitude. C doesn't. If it did, the stack would be more complicated, function calls would be more expensive, and programs would run a lot slower.

    If you know that you have an array but you won't know until runtime how big it will be, declare a pointer to it and use malloc() or calloc() to allocate the array from the heap.

    If you know at compile time how big an array is, you can declare its size at compile time. Even if the size is some complicated expression, as long as it can be evaluated at compile time, it can be used.

    /* A program that copies the argv array and all the pointed-to
    strings. It also deallocates all the copies. */
    #include <stdlib.h>
    #include <string.h>
    int main(int argc, char** argv)
    {
        char** new_argv;
        int i;
        /* Since argv[0] through argv[argc] are all valid, the
        program needs to allocate room for argc+1 pointers. */
        new_argv = (char**) calloc(argc+1, sizeof (char*));
        /* or malloc((argc+1) * sizeof (char*)) */
        printf("allocated room for %d pointers starting at %P\n", argc+1, new_argv);
        /* now copy all the strings themselves
        (argv[0] through argv[argc-1]) */
        for (i = 0; i < argc; ++i) {
            /* make room for '\0' at end, too */
        new_argv[i] = (char*) malloc(strlen(argv[i]) + 1);
            strcpy(new_argv[i], argv[i]);
            printf("allocated %d bytes for new_argv[%d] at %P, ""copied \"%s\"\n",
                    strlen(argv[i]) + 1, i, new_argv[i], new_argv[i]);
        }
        new_argv[argc] = NULL;
        /* To deallocate everything, get rid of the strings (in any
        order), then the array of pointers. If you free the array
        of pointers first, you lose all reference to the copied
        strings. */
        for (i = 0; i < argc; ++i) 
        {
            free(new_argv[i]);
            printf("freed new_argv[%d] at %P\n", i, new_argv[i]);
            argv[i] = NULL;
        }
        free(new_argv);
        printf("freed new_argv itself at %P\n", new_argv);
        return 0; 
    }
    View
  • 15.

    When would you use a pointer to a function?

    Answer:

    Pointers to functions are interesting when you pass them to other functions. A function that takes function pointers says, in effect, "Part of what I do can be customized. Give me a pointer to a function, and I'll call it when that part of the job needs to be done. That function can do its part for me." This is known as a "callback." It's used a lot in graphical user interface libraries, in which the style of a display is built into the library but the contents of the display are part of the application.

    As a simpler example, say you have an array of character pointers (char*s), and you want to sort it by the value of the strings the character pointers point to. The standard qsort() function uses function pointers to perform that task. qsort() takes four arguments

    1. a pointer to the beginning of the array,

    2. the number of elements in the array,

    3. the size of each array element, and

    4. a comparison function.

    and returns an int.

    The comparison function takes two arguments, each a pointer to an element. The function returns 0 if the pointed-to elements compare equal, some negative value if the first element is less than the second, and some positive value if the first element is greater than the second. A comparison function for integers might look like this:

    int icmp( const int *p1, const int *p2 )
    {
         return *p1 - *p2;
    }
    

    The sorting algorithm is part of qsort(). So is the exchange algorithm; it just copies bytes, possibly by calling memcpy() or memmove()qsort() doesn't know what it's sorting, so it can't know how to compare them. That part is provided by the function pointer.

    You can't use strcmp() as the comparison function for this example, for two reasons. The first reason is thatstrcmp()'s type is wrong; more on that a little later. The second reason is that it won't work. strcmp() takes two pointers to char and treats them as the first characters of two strings. The example deals with an array of character pointers (char*s), so the comparison function must take two pointers to character pointers(char*s). In this case, the following code might be an example of a good comparison function:

    int strpcmp( const void *p1, const void *p2 )
    {
         char * const *sp1 = (char * const *) p1;
         char * const *sp2 = (char * const *) p2;
         return strcmp( *sp1, *sp2 );
    }
    

    The call to qsort() might look something like this:

    qsort( array, numElements, sizeof( char * ), pf2 );

    qsort() will call strpcmp() every time it needs to compare two character pointers (char*s).

    Why can't strcmp() be passed to qsort(), and why were the arguments of strpcmp() what they were?

    A function pointer's type depends on the return type of the pointed-to function, as well as the number and types of all its arguments. qsort() expects a function that takes two constant void pointers:

    void qsort( void *base,
                size_t numElements,
                size_t sizeOfElement,
                int (*compFunct)( const void *, const void *) );
    

    Because qsort() doesn't really know what it's sorting, it uses a void pointer in its argument (base) and in the arguments to the comparison function. qsort()'s void* argument is easy; any pointer can be converted to a void* without even needing a cast. The function pointer is harder.

    For an array of character arrays, strcmp() would have the right algorithm but the wrong argument types. The simplest, safest way to handle this situation is to pass a function that takes the right argument types forqsort() and then casts them to the right argument types. That's what strpcmp() does.

    If you have a function that takes a char*, and you know that a char* and a void* are the same in every environment your program might ever work in, you might cast the function pointer, rather than the pointed- to function's arguments, in this way:

    char     table[ NUM_ELEMENTS ][ ELEMENT_SIZE ];
    /* ... */
    /* passing strcmp() to qsort for array of array of char */
    qsort( table, NUM_ELEMENTS, ELEMENT_SIZE,
      ( int (*)( const void *, const void * ) ) strcmp );
    

    Casting the arguments and casting the function pointer both can be error prone. In practice, casting the function pointer is more dangerous.

    The basic problem here is using void* when you have a pointer to an unknown type. C++ programs sometime solve this problem with templates.

    View
  • 16.

    How do you use a pointer to a function?

    Answer:

    The hardest part about using a pointer-to-function is declaring it. Consider an example. You want to create a pointer, pf, that points to the strcmp() function. The strcmp() function is declared in this way:

    int strcmp( const char *, const char * )

    To set up pf to point to the strcmp() function, you want a declaration that looks just like the strcmp()function's declaration, but that has *pf rather than strcmp:

    int (*pf)( const char *, const char * );

    Notice that you need to put parentheses around *pf. If you don't include parentheses, as in

    int *pf( const char *, const char * ); /* wrong */

    you'll get the same thing as this:

    (int *) pf( const char *, const char * ); /* wrong */

    That is, you'll have a declaration of a function that returns int*.

    After you've gotten the declaration of pf, you can #include <string.h> and assign the address of strcmp() to pf:

    pf = strcmp;

    or

    pf = & strcmp; /* redundant & */

    You don't need to go indirect on pf to call it:

    if ( pf( str1, str2 ) > 0 ) /* ... */

    View
  • 17.

    Can you add pointers together? Why would you?

    Answer:

    No, you can't add pointers together. If you live at 1332 Lakeview Drive, and your neighbor lives at 1364 Lakeview, what's 1332+1364? It's a number, but it doesn't mean anything. If you try to perform this type of calculation with pointers in a C program, your compiler will complain.

    The only time the addition of pointers might come up is if you try to add a pointer and the difference of two pointers:

    p = p + p2 - p1;

    which is the same thing as this:

    p = (p + p2) - p1.

    Here's a correct way of saying this:

    p = p + ( p2 - p1 );

    Or even better in this case would be this example:

    p += p2 - p1;

    View
  • 18.

    What does it mean when a pointer is used in an if statement?

    Answer:

    Any time a pointer is used as a condition, it means "Is this a non-null pointer?" A pointer can be used in an if, whilefor, or do/while statement, or in a conditional expression. It sounds a little complicated, but it's not.

    Take this simple case:

    if ( p )
    {
         /* do something */
    }
    else
    {
         /* do something else */
    }
    

    An if statement does the "then" (first) part when its expression compares unequal to zero. That is,

    if ( /* something */ )

    is always exactly the same as this:

    if ( /* something */ != 0 )

    That means the previous simple example is the same thing as this:

    if ( p != 0 )
    {
         /* do something (not a null pointer) */
    }
    else
    {
         /* do something else (a null pointer) */
    }
    

    This style of coding is a little obscure. It's very common in existing C code; you don't have to write code that way, but you need to recognize such code when you see it.

    View
  • 19.

    Is NULL always equal to 0(zero)?

    Answer:

    The answer depends on what you mean by "equal to." If you mean "compares equal to," such as

    if ( /* ... */ )
    {
         p = NULL;
    }
    else
    {
         p = /* something else */;
    }
    /* ... */
    if ( p == 0 )
    

    then yes, NULL is always equal to 0. That's the whole point of the definition of a null pointer.

    If you mean "is stored the same way as an integer zero," the answer is no, not necessarily. That's the most common way to store a null pointer. On some machines, a different representation is used.

    The only way you're likely to tell that a null pointer isn't stored the same way as zero is by displaying a pointer in a debugger, or printing it. (If you cast a null pointer to an integer type, that might also show a nonzero value.)

    View
  • 20.

    Is NULL always defined as 0(zero)?

    Answer:

    NULL is defined as either 0 or (void*)0. These values are almost identical; either a literal zero or a void pointer is converted automatically to any kind of pointer, as necessary, whenever a pointer is needed (although the compiler can't always tell when a pointer is needed).

    View

© 2017 QuizBucket.org