Sunday, 29 December 2013

Variables, Scope and Parameter passing


This post serves to explain what exactly pass by reference and pass by value mean in the context of programming and as to how heaps and stacks relate to them.

It is a known fact that both C and Java pass by value.
Let us consider the following segment of code:

int main () {
int a = 5;
int b = 4;
swap (&a, &b);
printf ( “%d : ” , a );
printf (“%d : ” , b );
return 0;
}
void swap ( int *x , int *y ) {
int temp = *x;
*x = *y;
*y = temp;
}
Listing 1

The above code swaps the values a and b so that a contains 4 and b contains 5.
The function call, swap (&a, &b); passes the reference of the variables a and b and this facilitates the actual swap of the values contained in the two variables. Note that references, &a and &b are themselves passed as values and this is precisely why we say function calls in C pass by value.

Local variables or Stack variables

Now, if we take a look at the variable temp in Listing 1, we can see that it is a local variable. Variables which are local in scope are allocated on the stack and when a function returns (or when the last instruction in the function has been executed), the local variables on the stack corresponding to that function are invalidated and the memory thus reclaimed is open for reallocation.

In Listing 1, variables a and b defined in the main function are local variables which are local in scope to the main function and thus reside on the stack. These variables remain stack-resident till the time the main does not return (until after return 0). Likewise, temp is a local variable defined in the swap method and its scope is confined to the swap method only. After the final instruction in swap has been executed temp is invalidated and the memory thus claimed may be re-allocated.

It is important to understand just how relevant this is sometimes and understand its relation to pass by value.

Consider Listing 2.
int sum ( int a , int b )
{
          int s = 0;
          s = a + b;
          return s;
}
int main () {
          int a = 4;
          int b = 5;
          int s = sum ( a, b );
          printf ( “sum: %d\n” , s );
}
Listing 2

Now, we can see that the values contained in a and b are passed to the method sum. It is important to understand here that in C, copies of the values contained in a and b are created and passed as function arguments, whereas in Java, the references of a and b are themselves passed as values. While this may be suggestive of pass by reference, the references are themselves treated as values and it is because of that we say both C and Java pass values.

Now, when the control returns from the function sum, variable s is out of scope and the memory reclaimed and after printf prints sum: 9, variables a and b will also be out of scope. Thus, we can see that stack-resident variables have a scope that is confined to the block where they have been defined.

Variables allocated on the Heap

In C, variables declared outside function body and the variables created using malloc are allocated on the Heap. In C, the variables declared outside function body have file-level scope and may be accessed by all methods contained in that file. Now, variables allocated using malloc remain allocated in the heap till the time you do not explicitly free them using ‘free’ method. In Java, objects allocated using the ‘new’ keyword are allocated on the heap and they are automatically garbage- collected by the JVM when they are no longer used.

Stack and Heap distribution and impact on variable scope

Each thread in a program has its own stack space while all the threads share the same heap space.

Consider the following block of code in Listing 3:
public String foo ( String city1 , String city2 ) {
String city3 = city1 + “,” + city2;
save (city3);
return city3;
}
Listing 3

As it may be apparent, each thread invokes the method foo with its own values for ‘city1’ and ‘city2’ and the method stores the concatenated result in a database using the save function and the reference to the result is also returned. Now, it is important to understand a few things here:

·       city3 is an object of String type and the statement,
String city3 = city1 + “,” + city2; initializes it.

·     Upon initialization, the object that city3 refers to resides on the heap while the reference to it (the name, city3) resides on the stack corresponding to the calling thread.

·   The heap, as mentioned above is shared by all threads but each thread has access to its own local portion of heap, called arena.
·     Now, the stack space corresponding to a thread may in itself be divided into multiple frames, each frame corresponding to a different function or method and bearing variables that are local in scope to that method.

Bullet 3 tells us that the object for a thread, Thread1 referred by ‘city3’ is stored on the heap arena corresponding to Thread1 and thus local in scope to Thread1. From bullet 4, we can see that the reference itself, that is, the name, ‘city3’ is local in scope to the function foo and thereby resident on the stack frame corresponding to foo in the stack space for Thread1.

As the value of the reference or address to the referred object is returned by foo, the copy of the reference, ‘city3’ is in itself invalidated whereas the object itself will remain existent till the time it is not reclaimed by JVM. This is to re-iterate that Java also exchanges data between methods by passing their values which may be data values or values of addresses.

Key things to remember:

1. Threads have their own stack space

2. Heap space is shared between threads but each thread has access to its own portion of the heap space where objects initialized by that thread (using ‘new’ or ‘malloc’ keywords or by any other method of object initialization such as the method shown above for String initialization) are resident until they are not freed explicitly by the user (e.g. using free method for malloc) or automatically (e.g. JVM garbage collection).

3. Methods in a Thread stack have their own stack frames which contain the variables local to those methods.

4. C and Java pass by value.
Two separate C programs have been given below that help us understand the above mentioned ideas. The first one is unreliable in that it passes values of references to local variables resident on its stack outside of its scope while the other works reliably by allocating the values to be passed on the heap by using the malloc keyword.

UNRELIABLE CODE:
#include <stdio.h>
  long power ( int *a );
  long doubler ( int *a );
 
  int main ( int argc , char *argv[] ) { 
    printf ( "Command line argument: %s\n" , argv[1] );
    int operand = *argv[1] - '0';
    printf ( "Entered cmdline operand :%d\n" , operand );
     
    long *result1;
    long *result2;
   
    result1 = power (&operand);
    printf ( "Power of %d: %lu\n", operand, *result1 );
   
    result2 = doubler (&operand);
    printf ( "Double of %d: %d\n", operand, *result2 );
    return NULL;
  }

 long power ( int *a ) {
    printf ( "*a : %d\n" , *a );
    long result1;
    result1 = (*a) * (*a);
    printf ( "*result1 : %lu\n" , result1 );
    return &result1;
  } 

  long doubler ( int *a ) {
    printf ( "*a : %d\n" , *a );
    int result2;
    result2 = (*a) * 2;
    printf ( "*result2 : %d\n" , result2 );
    return &result2;   
  }

RELIABLE CODE:

#include <stdio.h>

  long *power ( int *a );
  int *doubler ( int *a );
 
  int main ( int argc , char *argv[] ) {
 
    printf ( "Command line argument: %c\n" , *argv[1] );
    int operand = *argv[1] - '0';
    printf ( "Entered cmdline operand :%d\n" , operand );
     
    long *result1;
    int *result2;
   
    result1 = power (&operand);
    printf ( "Power of %d: %lu\n", operand, *result1 );
   
    result2 = doubler (&operand);
    printf ( "Double of %d: %d\n", operand, *result2 );
      
    return NULL;
  }
 
  long *power ( int *a ) {
    printf ( "*a : %d\n" , *a );
    long *result1 = (long *) malloc( sizeof (long *));
    *result1 = (*a) * (*a);
    printf ( "*result1 : %lu\n" , *result1 );
    return result1;
  }
 
  int *doubler ( int *a ) {
    printf ( "*a : %d\n" , *a );
    int *result2 = (long *) malloc( sizeof (int *));
    *result2 = (*a) * 2;
    printf ( "*result2 : %d\n" , *result2 );
    return result2;
   }

No comments:

Post a Comment