Thursday, January 9, 2014

Chapter:03 Std::Array

Fundamental
========

"std::array" is a class template for storing fixed-size sequence of objects.  This
has been introduced in the C++11 standard to replace "array" data structure. The major problem with the default "array" is it does not have the idea of its size. This causes a major problem as users need to store the size of the "array" which needs to be passed wherever along with the array base pointer. I mean size information is not well encapsulated within "array" data structure logic. However "array" structure is very popular because of its simplicity and  efficiency. So "std::array" has been designed so that user is able to use it instead of an "array", and moreover without loosing efficiency. On the other side "std::array" provides the interface similar to well behaved containers like "std::vector". Due to these flexibility, I believe that "std::array" would become very popular container class and it would replace the default "array". Following are key concepts about "std::array" container class:


1. Efficiency Comparable to default "array".
2. Interfaces available to perform low level operation as default "array".
3. Interfaces/Operation similar to the standard container class "std::vector".
4. Size information gets encapsulated within the object of class "std::array".



Uses
===

Let us now write a small program which demonstrates how we can use "std:array" in the place of default "array". The below program is self-explanatory and describes the basic usages of "std::array". The key point to be noted here is, user code need not to pass the size information in the argument. In addition to that, it behaves as a well defined container classes(for example std::vector) which allowed us to sort the elements easily.


This indeed encapsulates many things in one simple class.I must say it is
one of my favourite container class because of elegance and simplicity.


//Code Begins
#include<array>
#include<iostream>
#include<algorithm>

constexpr std::size_t size=5;
typedef std::array<int,size> myarray;

void double_entry(myarray& val)
{
    for(myarray::size_type i=0; i<val.size(); ++i)
        val[i] = 2*val[i];
}

void display(myarray& val)
{
    for(myarray::size_type i=0; i<val.size(); ++i)
        std::cout<<val.at(i)<<"\t\t";
    std::cout<<"\n";
}

void learn_std_array(void)
{
    myarray arr_x{9,10,5,4,3};
    display(arr_x);
    double_entry(arr_x);
    display(arr_x);
    std::sort(arr_x.begin(),arr_x.end());
    display(arr_x);
}

int main(int argc, const char* argv[])
{
    learn_std_array();
    return 0;
}

//Code Ends



The Output of the above program is as follows.


//Console Output Begins

$ ./test
9        10       5         4          3       
18      20      10        8          6       
6        8        10        18        20   

//Console Output Ends



If you are a diehard fan of old style "array”, do not feel disheartened. If you still like to use the array elements using pointer mechanism,"std::array" provides interface for you. That’s the beauty of this class. Additionaly I would also explain using debugger, how memory management of "std::array" works. I have already mentioned that this class uses stack memory instead of free store to make things simple and optimal. However with stack (local) memory, we should also be careful while returning something to caller scope.


The old rule is applicable here as well. If you need to return "std::array" from the function, return it by value and not by reference. However this would be bit costly as there is no concept like "move constructor" for the "std::array". Due to this it would copy all elements and its linear time operation. By the way this copy of all elements is applicable for default "array".


//Code Begins
#include<array>
#include<iostream>

constexpr std::size_t size=5;
typedef std::array<int,size> myarray;

/* Change the return type from myarray&    */
/* to myarray and check the output.The     */
/* below function is wrong and it contains */
/* the serious error. */
myarray& double_entry(myarray& val)
{
  auto *base=val.data();
 myarray arr_ret;
 auto *ret_base=arr_ret.data();
 for(myarray::size_type i=0; i<val.size(); ++i)
  *(ret_base+i)=2*(*(base+i));
  return arr_ret;
}

void display(myarray& val)
{
  for(myarray::size_type i=0; i<val.size(); ++i)
      std::cout<<val.at(i)<<"\t\t";
  std::cout<<"\n";
}

void learn_std_array(void)
{
 myarray arr_x{9,10,5,4,3};
 display(arr_x);
 myarray arr_y=double_entry(arr_x);
 display(arr_y);
}

int main(int argc, const char* argv[])
{
 learn_std_array();
 return 0;
}

//Code Ends



The Output of the above program is as follows.


//Console Output Begins
$ ./test(With myarray interface. This correct way to use )
9          10        5          4        3       
18        20        10        8        6

$ ./test(With myarray& interface. This is serious bug)
test.cpp: In function ‘myarray& double_entry(myarray&)’:
test.cpp:14:10: warning: reference to local variable
‘arr_ret’ returned [-Wreturn-local-addr]
  myarray arr_ret;
          ^
9        10        5        4        3       
-1999   32719   419673  0       0

//Console Output Ends



To understand the above behavior we need to know about how “std::array” actually manages its memory management. Again I believe gdb debugger log would help us understand better. I want to explain this with the correct code as I want to keep things simple while explaining these.




//Debugger Output Begins
(gdb) 12     for(myarray::size_type i=0; i<val.size(); ++i)

// This command would display all the local variables defined
// inside this function.
(gdb) info locals
i = 140737488347328
base = 0x7fffffffe0c0
arr_ret = {
  _M_elems = {2, 0, 4197341, 0, -139589008}
}
ret_base = 0x7fffffffe0e0


// This command would display all arguments passed to this function.
(gdb) info args
val = @0x7fffffffe0c0: {
  _M_elems = {9, 10, 5, 4, 3}
}


// The following command has been used to find out PID of the current
// program. This would be used to find out the stack segment region
// in the current process address space.
(gdb) info proc
process 6968
cmdline = './test'
cwd = './03_std array'
exe = './test'
(gdb) shell cat /proc/6968/maps|grep "stack"
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0    [stack]


// I have displayed these range address into decimal as hex is not
// so natural to humans. I have also printed the base addresses of
// the two arrays. These address should lie in the stack segment
// range which can be confirmed from the below output.
(gdb) p/d 0x7ffffffde000
$1 = 140737488216064
(gdb) p/d 0x7ffffffff000
$2 = 140737488351232
(gdb) p/d 0x7fffffffe0e0
$3 = 140737488347360
(gdb) p/d 0x7fffffffe0c0
$4 = 140737488347328

// The below command would display the current stack trace.
(gdb) backtrace
#0  double_entry (val=...) at test.cpp:12
#1  0x0000000000400a54 in learn_std_array () at test.cpp:28
#2  0x0000000000400a80 in main (argc=1, argv=0x7fffffffe208)

// By finish command, we come out from the current frame to its
// caller. We can see that it return the complete array which
// would copied into variable arr_y. This is expensive.
(gdb) finish
Run till exit from #0  double_entry (val=...) at test.cpp:12
learn_std_array () at test.cpp:29
29     display(arr_y);
Value returned is $5 = {_M_elems = {18, 20, 10, 8, 6}}
(gdb) next
18        20        10        8        6       
30    }


//Debugger Output Ends





No comments:

Post a Comment