Keyword

Result: 265 questions

What is page thrashing?

Answer:

Some operating systems (such as UNIX or Windows in enhanced mode) use virtual memory. Virtual memory is a technique for making a machine behave as if it had more memory than it really has, by using disk space to simulate RAM (random-access memory). In the 80386 and higher Intel CPU chips, and in most other modern microprocessors (such as the Motorola 68030, Sparc, and Power PC), exists a piece of hardware called the Memory Management Unit, or MMU.

The MMU treats memory as if it were composed of a series of "pages." A page of memory is a block of contiguous bytes of a certain size, usually 4096 or 8192 bytes. The operating system sets up and maintains a table for each running program called the Process Memory Map, or PMM. This is a table of all the pages of memory that program can access and where each is really located.

Every time your program accesses any portion of memory, the address (called a "virtual address") is processed by the MMU. The MMU looks in the PMM to find out where the memory is really located (called the "physical address"). The physical address can be any location in memory or on disk that the operating system has assigned for it. If the location the program wants to access is on disk, the page containing it must be read from disk into memory, and the PMM must be updated to reflect this action (this is called a "page fault"). Hope you're still with me, because here's the tricky part. Because accessing the disk is so much slower than accessing RAM, the operating system tries to keep as much of the virtual memory as possible in RAM. If you're running a large enough program (or several small programs at once), there might not be enough RAM to hold all the memory used by the programs, so some of it must be moved out of RAM and onto disk (this action is called "paging out").

The operating system tries to guess which areas of memory aren't likely to be used for a while (usually based on how the memory has been used in the past). If it guesses wrong, or if your programs are accessing lots of memory in lots of places, many page faults will occur in order to read in the pages that were paged out. Because all of RAM is being used, for each page read in to be accessed, another page must be paged out. This can lead to more page faults, because now a different page of memory has been moved to disk. The problem of many page faults occurring in a short time, called "page thrashing," can drastically cut the performance of a system.

Programs that frequently access many widely separated locations in memory are more likely to cause page thrashing on a system. So is running many small programs that all continue to run even when you are not actively using them. To reduce page thrashing, you can run fewer programs simultaneously. Or you can try changing the way a large program works to maximize the capability of the operating system to guess which pages won't be needed. You can achieve this effect by caching values or changing lookup algorithms in large data structures, or sometimes by changing to a memory allocation library which provides an implementation of malloc() that allocates memory more efficiently. Finally, you might consider adding more RAM to the system to reduce the need to page out.

View

When should the volatile modifier be used?

Answer:

The volatile modifier is a directive to the compiler's optimizer that operations involving this variable should not be optimized in certain ways. There are two special cases in which use of the volatile modifier is desirable. The first case involves memory-mapped hardware (a device such as a graphics adaptor that appears to the computer's hardware as if it were part of the computer's memory), and the second involves shared memory (memory used by two or more programs running simultaneously).

Most computers have a set of registers that can be accessed faster than the computer's main memory. A good compiler will perform a kind of optimization called "redundant load and store removal." The compiler looks for places in the code where it can either remove an instruction to load data from memory because the value is already in a register, or remove an instruction to store data to memory because the value can stay in a register until it is changed again anyway.

If a variable is a pointer to something other than normal memory, such as memory-mapped ports on a peripheral, redundant load and store optimizations might be detrimental. For instance, here's a piece of code that might be used to time some operation:

time_t time_addition(volatile const struct timer *t, int a)
{
        int     n;
        int     x;
        time_t  then;
        x = 0;
        then = t->value;
        for (n = 0; n < 1000; n++)
        {
                x = x + a;
        }
       return t->value - then;
}

In this code, the variable t->value is actually a hardware counter that is being incremented as time passes. The function adds the value of a to x 1000 times, and it returns the amount the timer was incremented by while the 1000 additions were being performed.

Without the volatile modifier, a clever optimizer might assume that the value of t does not change during the execution of the function, because there is no statement that explicitly changes it. In that case, there's no need to read it from memory a second time and subtract it, because the answer will always be 0. The compiler might therefore "optimize" the function by making it always return 0.

If a variable points to data in shared memory, you also don't want the compiler to perform redundant load and store optimizations. Shared memory is normally used to enable two programs to communicate with each other by having one program store data in the shared portion of memory and the other program read the same portion of memory. If the compiler optimizes away a load or store of shared memory, communication between the two programs will be affected.

View

Can a variable be both const and volatile?

Answer:

Yes. The const modifier means that this code cannot change the value of the variable, but that does not mean that the value cannot be changed by means outside this code. For instance, the timer structure was accessed through a volatile const pointer. The function itself did not change the value of the timer, so it was declared const. However, the value was changed by hardware on the computer, so it was declared volatile. If a variable is both const and volatile, the two modifiers can appear in either order.

View

When should a type cast be used?

Answer:

There are two situations in which to use a type cast. The first use is to change the type of an operand to an arithmetic operation so that the operation will be performed properly. The variable f1 is set to the result of dividing the integer i by the integer j. The result is 0, because integer division is used. The variable f2 is set to the result of dividing i by j as well. However, the (float) type cast causes i to be converted to a float. That in turn causes floating-point division to be used and gives the result 0.75.

#include <stdio.h>
main()
{
    int i = 3;
    int j = 4;
    float f1 = i / j;
    float f2 = (float) i / j;
    printf("3 / 4 == %g or %g depending on the type used.\n", f1, f2);
}

The second case is to cast pointer types to and from void * in order to interface with functions that expect or return void pointers. For example, the following line type casts the return value of the call to malloc() to be a pointer to a foo structure.

struct foo *p = (struct foo *) malloc(sizeof(struct foo));

View

When should a type cast not be used?

Answer:

A type cast should not be used to override a const or volatile declaration. Overriding these type modifiers can cause the program to fail to run correctly.

A type cast should not be used to turn a pointer to one type of structure or data type into another. In the rare events in which this action is beneficial, using a union to hold the values makes the programmer's intentions clearer.

View

What is the difference between declaring a variable and defining a variable?

Answer:

Declaring a variable means describing its type to the compiler but not allocating any space for it. Defining a variable means declaring it and also allocating space to hold the variable. You can also initialize a variable at the time it is defined. Here is a declaration of a variable and a structure, and two variable definitions, one with initialization:

extern int decl1;  /* this is a declaration */
struct decl2 
{
    int member;
}; /* this just declares the type--no variable mentioned */
int     def1 = 8;      /* this is a definition */
int     def2;          /* this is a definition */

To put it another way, a declaration says to the compiler, "Somewhere in my program will be a variable with this name, and this is what type it is." A definition says, "Right here is this variable with this name and this type."

A variable can be declared many times, but it must be defined exactly once. For this reason, definitions do not belong in header files, where they might get #included into more than one place in your program.

View

What is the benefit of using const for declaring constants?

Answer:

The benefit of using the const keyword is that the compiler might be able to make optimizations based on the knowledge that the value of the variable will not change. In addition, the compiler will try to ensure that the values won't be changed inadvertently.

Of course, the same benefits apply to #defined constants. The reason to use const rather than #define to define a constant is that a const variable can be of any type (such as a struct, which can't be represented by a #defined constant). Also, because a const variable is a real variable, it has an address that can be used, if needed, and it resides in only one place in memory.

View

How many parameters should a function have?

Answer:

There is no set number or "guideline" limit to the number of parameters your functions can have. However, it is considered bad programming style for your functions to contain an inordinately high (eight or more) number of parameters. The number of parameters a function has also directly affects the speed at which it is called—the more parameters, the slower the function call. Therefore, if possible, you should minimize the number of parameters you use in a function. If you are using more than four parameters, you might want to rethink your function design and calling conventions.

One technique that can be helpful if you find yourself with a large number of function parameters is to put your function parameters in a structure. Consider the following program, which contains a function namedprint_report() that uses 10 parameters. Instead of making an enormous function declaration and proto- type, the print_report() function uses a structure to get its parameters:

#include <stdio.h>
typedef struct
{
     int       orientation;
     char      rpt_name[25];
     char      rpt_path[40];
     int       destination;
     char      output_file[25];
     int       starting_page;
     int       ending_page;
     char      db_name[25];
     char      db_path[40];
     int       draft_quality;
} RPT_PARMS;
void main(void);
int print_report(RPT_PARMS*);
void main(void)
{
     RPT_PARMS rpt_parm; /* define the report parameter
                            structure variable */
     ...
     /* set up the report parameter structure variable to pass to the
       print_report() function */
       rpt_parm.orientation = ORIENT_LANDSCAPE;
     rpt_parm.rpt_name = "QSALES.RPT";
     rpt_parm.rpt_path = "C:\REPORTS";
     rpt_parm.destination = DEST_FILE;
     rpt_parm.output_file = "QSALES.TXT";
     rpt_parm.starting_page = 1;
     rpt_parm.ending_page = RPT_END;
     rpt_parm.db_name = "SALES.DB";
     rpt_parm.db_path = "C:\DATA";
     rpt_parm.draft_quality = TRUE;
     /* Call the print_report() function, passing it a pointer to the
     parameters instead of passing it a long list of 10 separate
        parameters. */
     ret_code = print_report(&rpt_parm);
     ...
}
int print_report(RPT_PARMS* p)
{
     int rc;
     ...
     /* access the report parameters passed to the print_report()
        function */
     orient_printer(p->orientation);
     set_printer_quality((p->draft_quality == TRUE) ? DRAFT : NORMAL);
     ...
     return rc;
}

The preceding example avoided a large, messy function prototype and definition by setting up a predefined structure of type RPT_PARMS to hold the 10 parameters that were needed by the print_report()function.  The only possible disadvantage to this approach is that by removing the parameters from the function definition, you are bypassing the compiler's capability to type-check each of the parameters for validity during the compile stage.

Generally, you should keep your functions small and focused, with as few parameters as possible to help with execution speed. If you find yourself writing lengthy functions with many parameters, maybe you should rethink your function design or consider using the structure-passing technique presented here. Additionally, keeping your functions small and focused will help when you are trying to isolate and fix bugs in your programs.

View

What is the difference between a string copy (strcpy) and a memory copy (memcpy)? When should each be used?

Answer:

The strcpy() function is designed to work exclusively with strings. It copies each byte of the source string to the destination string and stops when the terminating null character (\0) has been moved. On the other hand, the memcpy() function is designed to work with any type of data.

Because not all data ends with a null character, you must provide the memcpy() function with the number of bytes you want to copy from the source to the destination. The following program shows examples of both the strcpy() and the memcpy() functions:

#include <stdio.h>
#include <string.h>
typedef struct cust_str {
     int  id;
     char last_name[20];
     char first_name[15];
} CUSTREC;
void main(void);
void main(void)
{
     char*   src_string = "This is the source string";
     char    dest_string[50];
     CUSTREC src_cust;
     CUSTREC dest_cust;
     printf("Hello!  I'm going to copy src_string into dest_string!\n");
     /* Copy src_string into dest_string. Notice that the destination
        string is the first argument. Notice also that the strcpy()
        function returns a pointer to the destination string. */
     printf("Done! dest_string is: %s\n",
            strcpy(dest_string, src_string));
     printf("Encore! Let's copy one CUSTREC to another.\n");
     printf("I'll copy src_cust into dest_cust.\n");
     /* First, initialize the src_cust data members. */
     src_cust.id = 1;
     strcpy(src_cust.last_name, "Strahan");
     strcpy(src_cust.first_name, "Troy");
     /* Now, use the memcpy() function to copy the src_cust structure to
        the dest_cust structure. Notice that, just as with strcpy(), the
        destination comes first. */
     memcpy(&dest_cust, &src_cust, sizeof(CUSTREC));
     printf("Done! I just copied customer number #%d (%s %s).",
               dest_cust.id, dest_cust.first_name, dest_cust.last_name);
}

When dealing with strings, you generally should use the strcpy() function, because it is easier to use with strings. When dealing with abstract data other than strings (such as structures), you should use the memcpy() function.

View

How can I remove the leading spaces from a string?

Answer:

The C language does not provide a standard function that removes leading spaces from a string. It is easy, however, to build your own function to do just this. you can easily construct a custom function that uses the rtrim() function in conjunction with the standard C library function strrev() to remove the leading spaces from a string. Look at how this task is performed:

#include <stdio.h>
#include <string.h>
void main(void);
char* ltrim(char*);
char* rtrim(char*);
void main(void)
{
     char* lead_str = "          This string has leading spaces in it.";
     /* Show the status of the string before calling the ltrim()
        function. */
     printf("Before calling ltrim(), lead_str is '%s'\n", lead_str);
     printf("and has a length of %d.\n", strlen(lead_str));
     /* Call the ltrim() function to remove the leading blanks. */
     ltrim(lead_str);
     /* Show the status of the string
        after calling the ltrim() function. */
     printf("After calling ltrim(), lead_str is '%s'\n", lead_str);
     printf("and has a length of %d.\n", strlen(lead_str));
}
/* The ltrim() function removes leading spaces from a string. */
char* ltrim(char* str)
{
     strrev(str);    /* Call strrev() to reverse the string. */
     rtrim(str);     /* Call rtrim() to remove the "trailing" spaces. */
     strrev(str);    /* Restore the string's original order. */
     return str;     /* Return a pointer to the string. */
}
/* The rtrim() function removes trailing spaces from a string. */
char* rtrim(char* str)
{
     int n = strlen(str) - 1;     /* Start at the character BEFORE
                                     the null character (\0). */
     while (n>0)            /* Make sure we don't go out of bounds... */
     {
          if (*(str+n) != ' ')    /* If we find a nonspace character: */
          {
               *(str+n+1) = '\0'; /* Put the null character at one
                                     character past our current
                                     position. */
               break;             /* Break out of the loop. */
          }
          else      /* Otherwise, keep moving backward in the string. */
               n--;
     }
     return str;                  /* Return a pointer to the string. */
}

Notice that the ltrim() function performs the following tasks: First, it calls the standard C library functionstrrev(), which reverses the string that is passed to it. This action puts the original string in reverse order, thereby creating "trailing spaces" rather than leading spaces. Now, the rtrim() function is used to remove the "trailing spaces" from the string. After this task is done, the strrev() function is called again to "reverse" the string, thereby putting it back in its original order.

View

How many levels of pointers can you have?

Answer:

The answer depends on what you mean by "levels of pointers." If you mean "How many levels of indirection can you have in a single declaration?" the answer is "At least 12."

int i = 0;

int *ip01 = & i;

int **ip02 = & ip01;

int ***ip03 = & ip02;

int ****ip04 = & ip03;

int *****ip05 = & ip04;

int ******ip06 = & ip05;

int *******ip07 = & ip06;

int ********ip08 = & ip07;

int *********ip09 = & ip08;

int **********ip10 = & ip09;

int ***********ip11 = & ip10;

int ************ip12 = & ip11;

************ip12 = 1; /* i = 1 */

If you mean "How many levels of pointer can you use before the program gets hard to read," that's a matter of taste, but there is a limit. Having two levels of indirection (a pointer to a pointer to something) is common. Any more than that gets a bit harder to think about easily; don't do it unless the alternative would be worse.

If you mean "How many levels of pointer indirection can you have at runtime," there's no limit. This point is particularly important for circular lists, in which each node points to the next. Your program can follow the pointers forever.

Consider the following program "A circular list that uses infinite indirection".

/* Would run forever if you didn't limit it to MAX */
#include <stdio.h>
struct circ_list
{
        char    value[ 3 ];     /* e.g., "st" (incl '\0') */
        struct circ_list        *next;
};
struct circ_list    suffixes[] = {
        "th", & suffixes[ 1 ], /* 0th */
        "st", & suffixes[ 2 ], /* 1st */
        "nd", & suffixes[ 3 ], /* 2nd */
        "rd", & suffixes[ 4 ], /* 3rd */
        "th", & suffixes[ 5 ], /* 4th */
        "th", & suffixes[ 6 ], /* 5th */
        "th", & suffixes[ 7 ], /* 6th */
        "th", & suffixes[ 8 ], /* 7th */
        "th", & suffixes[ 9 ], /* 8th */
        "th", & suffixes[ 0 ], /* 9th */
        };
#define MAX 20
int main()
{
     int i = 0;
     struct circ_list    *p = suffixes;
     while (i <= MAX) 
     {
             printf( "%d%s\n", i, p->value );
             ++i;
             p = p->next;
     }
     return 0;
}

Each element in suffixes has one suffix (two characters plus the terminating NUL character) and a pointer to the next element. next is a pointer to something that has a pointer, to something that has a pointer, ad infinitum.

The example is dumb because the number of elements in suffixes is fixed. It would be simpler to have an array of suffixes and to use the i%10'th element. In general, circular lists can grow and shrink.

View

When is a null pointer used?

Answer:

The null pointer is used in three ways:

1. To stop indirection in a recursive data structure.

2. As an error value.

3. As a sentinel value.

1. Using a Null Pointer to Stop Indirection or Recursion

Recursion is when one thing is defined in terms of itself. A recursive function calls itself. The following factorial function calls itself and therefore is considered recursive:

/* Dumb implementation; should use a loop */
unsigned factorial( unsigned i )
{
     if ( i == 0 || i == 1 )
     {
          return 1;
     }
     else
     {
          return i * factorial( i - 1 );
     }
}

A recursive data structure is defined in terms of itself. The simplest and most common case is a (singularly) linked list. Each element of the list has some value, and a pointer to the next element in the list:

struct string_list
{
     char    *str;   /* string (in this case) */
     struct string_list      *next;
};

There are also doubly linked lists (which also have a pointer to the preceding element) and trees and hash tables and lots of other neat stuff. You'll find them described in any good book on data structures. You refer to a linked list with a pointer to its first element. That's where the list starts; where does it stop? This is where the null pointer comes in. In the last element in the list, the next field is set to NULL when there is no following element. To visit all the elements in a list, start at the beginning and go indirect on the next pointer as long as it's not null:

while ( p != NULL )
{
     /* do something with p->str */
     p = p->next;
}

Notice that this technique works even if p starts as the null pointer.

2. Using a Null Pointer As an Error Value

The second way the null pointer can be used is as an error value. Many C functions return a pointer to some object. If so, the common convention is to return a null pointer as an error code:

if ( setlocale( cat, loc_p ) == NULL )
{
     /* setlocale() failed; do something */
     /* ... */
}

This can be a little confusing. Functions that return pointers almost always return a valid pointer (one that doesn't compare equal to zero) on success, and a null pointer (one that compares equal to zero) pointer on failure. Other functions return an int to show success or failure; typically, zero is success and nonzero is failure. That way, a "true" return value means "do some error handling":

if ( raise( sig ) != 0 ) {
        /* raise() failed; do something */
        /* ... */
}

The success and failure return values make sense one way for functions that return ints, and another for functions that return pointers. Other functions might return a count on success, and either zero or some negative value on failure. As with taking medicine, you should read the instructions first.

Using a Null Pointer As a Sentinel Value

The third way a null pointer can be used is as a "sentinel" value. A sentinel value is a special value that marks the end of something. For example, in main(), argv is an array of pointers. The last element in the array (argv[argc]) is always a null pointer. That's a good way to run quickly through all the elements:

/* A simple program that prints all its arguments. 
It doesn't use argc ("argument count"); instead, 
it takes advantage of the fact that the last value 
in argv ("argument vector") is a null pointer. */
#include <stdio.h>
#include <assert.h>
int
main( int argc, char **argv)
{
        int i;
        printf("program name = \"%s\"\n", argv[0]);
        for (i=1; argv[i] != NULL; ++i)
                printf("argv[%d] = \"%s\"\n", i, argv[i]);
        assert(i == argc);    
        return 0; 
}
View

When is a void pointer used?

Answer:

void pointer is used for working with raw memory or for passing a pointer to an unspecified type.

Some C code operates on raw memory. When C was first invented, character pointers (char *) were used for that. Then people started getting confused about when a character pointer was a string, when it was a character array, and when it was raw memory.

For example, strcpy() is used to copy data from one string to another, and strncpy() is used to copy at most a certain length string to another:

char *strcpy( char *str1, const char *str2 );

char *strncpy( char *str1, const char *str2, size_t n );

memcpy() is used to move data from one location to another:

void *memcpy( void *addr1, void *addr2, size_t n );

void pointers are used to mean that this is raw memory being copied. NUL characters (zero bytes) aren't significant, and just about anything can be copied. Consider the following code:

#include "thingie.h"    /* defines struct thingie */
struct thingie  *p_src, *p_dest;
/* ... */
memcpy( p_dest, p_src, sizeof( struct thingie) * numThingies );

This program is manipulating some sort of object stored in a struct thingie. p1 and p2 point to arrays, or parts of arrays, of struct thingies. The program wants to copy numThingies of these, starting at the one pointed to by p_src, to the part of the array beginning at the element pointed to by p_destmemcpy() treatsp_src and p_dest as pointers to raw memory; sizeof( struct thingie) * numThingies is the number of bytes to be copied.

View

Can you subtract pointers from each other? Why would you?

Answer:

If you have two pointers into the same array, you can subtract them. The answer is the number of elements between the two elements.

Consider the street address analogy presented in the introduction of this chapter. Say that I live at 118 Fifth Avenue and that my neighbor lives at 124 Fifth Avenue. The "size of a house" is two (on my side of the street, sequential even numbers are used), so my neighbor is (124-118)/2 (or 3) houses up from me. (There are two houses between us, 120 and 122; my neighbor is the third.) You might do this subtraction if you're going back and forth between indices and pointers.

You might also do it if you're doing a binary search. If p points to an element that's before what you're looking for, and q points to an element that's after it, then (q-p)/2+p points to an element between p and q. If that element is before what you want, look between it and q. If it's after what you want, look between p and it.

(If it's what you're looking for, stop looking.)

You can't subtract arbitrary pointers and get meaningful answers. Someone might live at 110 Main Street, but I can't subtract 110 Main from 118 Fifth (and divide by 2) and say that he or she is four houses away!

If each block starts a new hundred, I can't even subtract 120 Fifth Avenue from 204 Fifth Avenue. They're on the same street, but in different blocks of houses (different arrays).

C won't stop you from subtracting pointers inappropriately. It won't cut you any slack, though, if you use the meaningless answer in a way that might get you into trouble.

When you subtract pointers, you get a value of some integer type. The ANSI C standard defines a typedef,ptrdiff_t, for this type. (It's in <stddef.h>.) Different compilers might use different types (int or long or whatever), but they all define ptrdiff_t appropriately.

Below is a simple program that demonstrates this point. The program has an array of structures, each 16 bytes long. The difference between array[0] and array[8] is 8 when you subtract struct stuff pointers, but 128 (hex 0x80) when you cast the pointers to raw addresses and then subtract.

If you subtract 8 from a pointer to array[8], you don't get something 8 bytes earlier; you get something 8 elements earlier.

#include <stdio.h>
#include <stddef.h>
struct stuff {
        char    name[16];
        /* other stuff could go here, too */
};
struct stuff array[] = {
        { "The" },
        { "quick" },
        { "brown" },
        { "fox" },
        { "jumped" },
        { "over" },
        { "the" },
        { "lazy" },
        { "dog." },
        { "" }
};
int main()
{
        struct stuff    *p0 = & array[0];
        struct stuff    *p8 = & array[8];
        ptrdiff_t       diff = p8 - p0;
        ptrdiff_t       addr_diff = (char*) p8 - (char*) p0;
        /* cast the struct stuff pointers to void* */
        printf("& array[0] = p0 = %P\n", (void*) p0);
        printf("& array[8] = p8 = %P\n", (void*) p8);
        /* cast the ptrdiff_t's to long's
        (which we know printf() can handle) */
        printf("The difference of pointers is %ld\n",
          (long) diff);
        printf("The difference of addresses is %ld\n",
          (long) addr_diff);
        printf("p8 - 8 = %P\n", (void*) (p8 - 8));
        
        printf("p0 + 8 = %P (same as p8)\n", (void*) (p0 + 8));
        return 0;  
}
View

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

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

Explain what is a $scope in AngularJS

Answer:

Scope is an object that refers to the application model. It is an execution context for expressions. Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can watch expressions and propagate events. Scopes are objects that refer to the model. They act as glue between controller and view.

This question is important as it will judge a persons knowledge about a $scope object, and it is one of the most important concepts in AngularJS. Scope acts like a bridge between view and model.

Source: https://docs.angularjs.org/guide/scope

View

How would you programatically change or adapt the template of a directive before it is executed and transformed?

Answer:

You would use the compile function. The compile function gives you access to the directive’s template before transclusion occurs and templates are transformed, so changes can safely be made to DOM elements. This is very useful for cases where the DOM needs to be constructed based on runtime directive parameters.

Read more about it here.

View

Explain what is scope in AngularJS ?

Answer:

Scope refers to the application model, it acts like glue between application controller and the view.  Scopes are arranged in hierarchical structure and impersonate the DOM ( Document Object Model) structure of the application.  It can watch expressions and propagate events.

View

Explain what is directive and Mention what are the different types of Directive?

Answer:

During compilation process when specific HTML constructs are encountered a behaviour or function is triggered, this function is referred as directive.  It is executed when the compiler encounters it in the DOM.

Different types of directives are

  • Element directives
  • Attribute directives
  • CSS class directives
  • Comment directives
View

© 2017 QuizBucket.org