I know that the global variable is stored in memory, and the local variable is saved in the stack, and the stack will be destroyed after each use.
But I do not know how an array is saved in memory.
I tried to declare a global array:
int tab[5]={10,9,12,34,30};
and at the end I read the contents of the memory, I mean, that after the execution of the code, I read the contents of the memory, (e.g I'm working on a microcontroller, and I know where the data is saving) when I declare a global variable for example a = 10; and when I read the contents of the memory I find the value 10 in the memory, but I do not find the content of the table that is 10,9,12,34,30
I want to understand where the array content is it save in memory?
I work on Aurix Infineon, and I use Hightec as a compiler, I execute my code directly on the aurix, I read the memorition like this:
const volatile unsigned char * mem_start = 0xd0000000;
#define size ((ptrdiff_t) 0xfff)
unsigned char bufferf [size];
code ();
main(){
...
for (int e = 0; e < sizeof (bufferf); e ++)
bufferf [e] = * (mem_start + e); // read memory
}
The answer to the question of where arrays, or indeed any variables, are stored depends on whether you are considering the abstract machine or the real hardware. The C standard specifies how things work on the abstract machine, but a conforming compiler is free to do something else on the real hardware (e.g., due to optimization) if the observable effects are the same.
(On the abstract machine, arrays are generally stored in the same place as other variables declared in the same scope would be.)
For example, a variable might be placed in a register instead of the stack, or it might be optimized away completely. As a programmer you generally shouldn't care about this, since you can also just consider the abstract machine. (Admittedly there may be some cases where you do care about it, and on microcontrollers with very limited RAM one reason might be that you have to be very frugal about using the stack, etc.)
As for your code for reading the memory: it cannot possibly work. If size is the size of the memory available to variables, you cannot fit the array bufferf[size] in that memory together with everything else.
Fortunately, copying the contents of the memory to a separate buffer is not needed. Consider your line bufferf[e] = *(mem_start + e) – since you can already read an arbitrary index e from memory at mem_start, you can use *(mem_start + e) (or, better, mem_start[e], which is exactly equivalent) directly everywhere you would use bufferf[e]! Just treat mem_start as pointing to the first element of an array of size bytes.
Also note that if you are programmatically searching for the contents of the array tab, its elements are ints, so they are more than one byte each – you won't simply find five adjacent bytes with those values.
(Then again, you can also just take the address of tab[0] and find out where it is stored, i.e., ((unsigned char *) tab) - mem_start is the index of tab in mem_start. However, here observing it may change things due to the aforementioned optimization.)
global variable is stored in memory, and the local variable is saved in the stack
This is false.
Global variables are sometimes kept in registers, not only in static memory.
If the function is recursive the compiler may choose to use the stack or not. If the function is tail recursive it is no need to use the stack as there is no continuation after the function returns and one can use the current frame for the next call.
There are mechanical methods to convert the code in continuation passing style and in this equivalent form it is evaluated stackless.
There are lots of mathematical models of computation and not all use the stack. The code can be converted from one model to other keeping the same result after evaluation.
You got these informations from books written in the 70s, 80s and in the meantime the process of evaluation of code was much improved (using methods that were theoretical in the 30s but nowadays are implemented in systems).
This program may get you closer to what you want:
int tab[] = {10, 9, 12, 34, 30};
// Used to get the number of elements in an array
#define ARRAY_SIZE(array) \
(sizeof(array) / sizeof(array[0]))
// Used to suppress compiler warnings about unused variables
#define UNUSED(x) \
(void)(x)
int main(void) {
int *tab_ptr;
int tab_copy[ARRAY_SIZE(tab)];
tab_ptr = tab;
UNUSED(tab_ptr);
for (int index = 0; index < ARRAY_SIZE(tab_copy); index++) {
tab_copy[index] = tab[index];
}
return 0;
}
I don't have the Hightec environment to test this, nor do I have an Aurix to run it on. However, this may give you a starting point that helps you obtain the information you desire.
It looks like the Infineon Aurix is a 32-bit little-endian machine. This means that an int will be 32-bits wide and the first byte will store the first 8 bits of the value. For example, the value 10 would be stored like this:
0a 00 00 00
If you try to read this memory as char, you will get 0x0a, 0x00, 0x00, and 0x00.
Related
I have a question regarding memory allocation on the basic C program.
#include <stdio.h>
int main()
{
int iarray[3];
char carray[3];
printf("%p\n", &iarray); // b8
printf("%p\n", &iarray+1); // c4
printf("%p\n", &carray); // c5
return 0;
}
given the code above, you can see that &iarray+1 and &carray have a difference of one byte which I'm not sure for which purpose or what in it, why does the compiler assign an unused byte between the two arrays?
I thought maybe it uses to know the array size, but I understood that sizeof is a compile-time function that knows the size without allocation of real memory, so there no use for storing the array size
Note: The output can be seen on the comments of each printf.
b8
c4
c5
Playground: https://onlinegdb.com/cTdzccpDvI
Thanks.
Compilers are free to arrange variables in memory any way that they see fit. Typically, they will be placed at memory offsets whose value is a multiple of the variable's size, for example a 4 byte int or int array will start at an address which is a multiple of 4.
In this case, you have an int array starting at an address which is a multiple of 4, followed by an unused byte, followed by a char array of size 3. In theory, an int or long could immediately follow the char array in memory if it was defined as the next available address is a multiple of 8.
From your output it looks like the stack is arranged like this for these local variables:
b8-bb: 1st integer of iarray
bc-bf: 2nd integer of iarray
c0-c3: 3rd integer of iarray
c4: padding probably, only compiler knows
c5-c7: carray
Now when you do &iarray+1 You are taking the address of an array int[3], and adding +1 of that array type to it. In other words, you are getting the address of the next int[3] array, which indeed would be at c4 (but isn't because there's just one int[3]).
This code is actually valid. You must not dereference this pointer, but because it points exactly +1 past the iarray, having the pointer and printing its value is legal (in other words, not Undefined Behavior, like &iarray+2 would be).
If you also print this:
printf("%p\n", iarray+1);
You should get result bc, because now you take pointer of type int (iarray is treated as pointer to int), add 1 to that, getting the next int.
The reason this happens is the particular compiler you are using allocates memory from high addresses to low in this particular situation.
The compiler analyzes the main routine and see it needs 3 int for iarray and 3 char for carray. For whatever reason, it decides to work on carray first.
The compiler starts with a planned stack frame that is required to have 16-byte alignment at certain points. Additional data is needed on the stack with the result that the point where the compiler starts putting local variables is at an address that is 8 modulo 16 (so its hexadecimal representation ends in 8). That is, from some address like 7FFC565E90A8 and up, memory is used for managing the stack frame. The first bytes for local objects will be at 7FFC565E90A7, 7FFC565E90A6, 7FFC565E90A5, 7FFC565E90A4, and so on.
The compiler takes the first three bytes of that space for carray. Recall we are working from high addresses to low addresses. (For historical reasons, that is the direction that stacks grow; some high address is assigned as the starting point, and new data is put in lower addresses.) So carray is put at address 7FFC565E90A5. It fills the bytes at 7FFC565E90A5, 7FFC565E90A6, and 7FFC565E90A7.
Then the compiler needs to assign 12 bytes for iarray. The next available 12 bytes are from 7FFC565E9099 to 7FFC565E90A4. However, the int elements in iarray require 4-byte alignment, so they cannot start at 7FFC565E9099. Therefore, the compiler adjusts to have them start at 7FFC565E9098. Then iarray fills bytes from 7FFC565E9098 to 7FFC565E90A3, and 7FFC565E90A4 is unused.
Note that in other situations, the compiler may arrange local objects in different ways. When you have multiple objects with different alignments, the compiler may choose to cluster all objects with the same alignment to reduce the number of places it needs to insert padding. A compiler could also choose to allocate memory for objects in alphabetical order by their names. Or it could do it in the order it happens to store them in its hash table. Or some combination of these things, such as clustering all objects by alignment requirement but then sorting by name within each cluster.
This behavior is purely (compiler) implementation defined. What probably happens is this:
When a function (main() in this case) is invoked which has local variables, memory for those variables are allocated on the stack. In this case, 15 bytes are needed, but it is likely that 4-byte alignment is required for the stack allocation, so that 16 bytes are allocated.
It is also likely that the int-array must be 4-byte aligned. Hence the address of the int array is a multiple of 4. The char-array does not have any alignment requirements so it can be placed anywhere in the 4 remaining bytes.
So in short, the additional byte is unused, but allocated due to alignment.
I have a piece of memory I am "guarding", defined by
typedef unsigned char byte;
byte * guardArea;
size_t guardSize;
byte * guardArea = getGuardArea();
size_t guardSize = getGuardSize();
An acceptable implementation for the sake of this would be:
size_t glGuardSize = 1024; /* protect an area of 1kb */
byte * getGuardArea()
{
return malloc( glGuardSize );
}
size_t getGuardSize()
{
return glGuardSize;
}
Can the following snippet return true for any pointer (from a different malloc, from the stack etc)?
if ( ptr >= guardArea && ptr < (guardArea + guardSize)) {
return true;
}
The standard states that:
values within the area will return true. (When ptr was a member, all acts correctly.)
pointers will be distinct (a == b only if they are the same).
all addresses within the byte array can be accessed by incrementing the base.
any pointer can be converted to and from a char *, without damage.
So I can't understand how the result could be true for any pointer from a different object (as it would break the distinct rule for one of the pointers within the area).
Edit:
What is the use case?
The ability to detect whether a pointer is within a region is really important, at some point code is written
if ( isInMyAreaOfInterest( unknownPointer ) ) {
doMySpecialThing( unknownPointer );
} else {
doSomethingElse( unknownPointer );
}
I think the language needs to support the developer by making such constructs simple and obvious, and our interpretation of the standard, is that the developer needs to cast to int. Due to the "undefined behavior" of pointer comparisons of distinct objects.
I was hoping for some clarity of why I can't do what I would like (my snippet), as all the posts on SO I found say that the standard claims undefined behavior, without any explanation, or examples of why the standard is better than how I would like it to work.
At the moment, we have a rule, we are neither understanding why the rule exists, or questioning if the rule is helping us
Example posts:
SO: checking if a pointer is in a malloced area
SO: C compare pointers
It is still possible for an allocation to generate a pointer that satisfies the condition despite the pointer not pointing into the region. This will happen, for example, on an 80286 in protected mode, which is used by Windows 3.x in Standard mode and OS/2 1.x.
In this system, pointers are 32-bit values, split into two 16-bit parts, traditionally written as XXXX:YYYY. The first 16-bit part (XXXX) is the "selector", which chooses a bank of 64KB. The second 16-bit part (YYYY) is the "offset", which chooses a byte within that 64KB bank. (It's more complicated than this, but let's just leave it at that for the purpose of this discussion.)
Memory blocks larger than 64KB are broken up into 64KB chunks. To move from one chunk to the next, you add 8 to the selector. For example, the byte after 0101:FFFF is 0109:0000.
But why do you add 8 to move to the next selector? Why not just increment the selector? Because the bottom three bits of the selector are used for other things.
In particular, the bottom bit of the selector is used to choose the selector table. (Let's ignore bits 1 and 2 since they are not relevant to the discussion. Assume for convenience that they are always zero.)
There are two selector tables, the Global Selector Table (for memory shared across all processes) and the Local Selector Table (for memory private to a single process). Therefore, the selectors available for process private memory are 0001, 0009, 0011, 0019, etc. Meanwhile, the selectors available for global memory are 0008, 0010, 0018, 0020, etc. (Selector 0000 is reserved.)
Okay, now we can set up our counter-example. Suppose guardArea = 0101:0000 and guardSize = 0x00020000. This means that the guarded addresses are 0101:0000 through 0101:FFFF and 0109:0000 through 0109:FFFF. Furthermore, guardArea + guardSize = 0111:0000.
Meanwhile, suppose there is some global memory that happens to be allocated at 0108:0000. This is a global memory allocation because the selector is an even number.
Observe that the global memory allocation is not part of the guarded region, but its pointer value does satisfy the numeric inequality 0101:0000 <= 0108:0000 < 0111:0000.
Bonus chatter: Even on CPU architectures with a flat memory model, the test can fail. Modern compilers take advantage of undefined behavior and optimize accordingly. If they see a relational comparison between pointers, they are permitted to assume that the pointers point into the same array (or one past the last element of that array). Specifically, the only pointers that can legally be compared with guardArea are the ones of the form guardArea, guardArea+1, guardArea+2, ..., guardArea + guardSize. For all of these pointers, the condition ptr >= guardArea is true and can therefore be optimized out, reducing your test to
if (ptr < (guardArea + guardSize))
which will now be satisfied for pointers that are numerically less than guardArea.
Moral of the story: This code is not safe, not even on flat architectures.
But all is not lost: The pointer-to-integer conversion is implementation-defined, which means that your implementation must document how it works. If your implementation defines the pointer-to-integer conversion as producing the numeric value of the pointer, and you know that you are on a flat architecture, then what you can do is compare integers rather than pointers. Integer comparisons are not constrained in the same way that pointer comparisons are.
if ((uintptr_t)ptr >= (uintptr_t)guardArea &&
(uintptr_t)ptr < (uintptr_t)guardArea + (uintptr_t)guardSize)
Yes.
void foo(void) {}
void(*a) = foo;
void *b = malloc(69);
uintptr_t ua = a, ub = b;
ua and ub are in fact permitted to have the same value. This occurred frequently on segmented systems (like MS-DOS) which might put code and data in separate segments.
I'm learning about pointers and have been told this: "The purpose of pointers is to allow you to manually, directly access a block of memory."
Say I have int var = 5;. Can't I use the variable 'var' to access the block of memory where the value 5 is stored, since I can change the value of the variable whenever I want var = 6;? Do I really need a pointer when I can access any variable's value just by using its variable, instead of using a pointer that points to the address where the value is stored?
"The purpose of pointers is to allow you to manually, directly access a block of memory."
This is not always true. Consider
*(int*)(0x1234) = some_value;
this is "direct" memory access. Though
int a = some_value, *ptr = &a;
*ptr = some_other_value;
you are now accessing a indirectly.
Can't I use the variable 'var' to access the block of memory where the
value 5 is stored, since I can change the value of the variable
whenever I want var = 6; ?
Surely; but the semantics is different.
Do I really need a pointer when I can access any variable's value just by using its variable, instead of using a pointer that points to the address where the value is stored?
No, you don't. Consider the first example: within the scope where a has been declared, modifying its value through ptr is rather pointless! However, what if you are not within the scope of a? That is
void foo(int x)
{
x = 5;
}
int main(void)
{
int x = 10;
foo(x);
}
In foo, when you do x = 5, there is an ambiguity: do you want to modify foo::x or main::x? In the latter case that has to be "requested" explicitly and the fact that happens through pointers -or, better, through indirection- is a coincidence and a language choice. Other languages have others.
Pointer types have some traits that make them really useful:
It's guaranteed that a pointer will be so large that it can hold any address that is supported by the architecture (on x86, that is 32 bits a.k.a. 4 bytes, and an x64 64 bits a.k.a. 8 bytes).
Dereferencing and indexing the memory is done per object, not per byte.
int buffer[10];
char*x = buffer;
int*y = (int*)buffer;
That way, x[1] isn't y[1]
Both is not guaranteed if you use simple ints to hold your values. The first trait is at least guaranteed by uintptr_t (not by size_t though, although most of the time they have the same size - except that size_t can be 2 bytes in size on systems with segmented memory layout, while uintptr_t is still 4 bytes in size).
While using ints might work at first, you always:
have to turn the value into a pointer
have to dereference the pointer
and have to make sure that you don't go beyond certain values for your "pointer". For a 16 bit int, you cannot go beyond 0xFFFF, for 32 bit it's 0xFFFF FFFF - once you do, your pointer might overflow without you noticing it until it's too late.
That is also the reason why linked lists and pointers to incomplete types work - the compiler already knows the size of the pointers you are going to you, and just allocates memory for them. All pointers have the same size (4 or 8 bytes on 32-bit/64-bit architectures) - the type that you assign them just tells the compiler how to dereference the value. char*s take up the same space as void*s, but you cannot dereference void*s. The compiler won't let you.
Also, if you are just dealing with simple integers, there's a good chance that you will slow down your program significantly do to something called "aliasing", which basically forces the compiler to read the value of a given address all the time. Memory accesses are slow though, so you want to optimized these memory accesses out.
You can compare a memory address to a street address:
If you order something, you tell the shop your address, so that they can send you what you bought.
If you don't want to use your address, you have to send them your house, such that they can place the parcel inside.
Later they return your house to you. This is a bit more cumbersome than using the address!
If you're not at home, the parcel can be delivered to your neighbor if they have your address, but this is not possible if
you sent them your house instead.
The same is true for pointers: They are small and can be transported easily, while the object they point to
might be large, and less easily transportable.
With pointer arithmetics, pointers can also be used to access other objects than the one they originally pointed to.
You call a function from main() or from another function, the function you called can only return 1 value.
Let say you want 3 values changed, you pass them to the called function as pointers. That way you don't have to use global values.
One possible advantage is that it can make it easier to have one function modify a variable that will be used by many other functions. Without pointers, your best option is for the modifying function to return a value to the caller and then to pass this value to the other functions. That can lead to a lot of passing around. Instead, you can give the modifying function a pointer where it stores its output, and all the other functions directly access that memory address. Kind of like global variables.
This question already has answers here:
Order of local variable allocation on the stack
(10 answers)
Closed 4 years ago.
I'm quite confused about how the local variables are ordered on the stack. I understand, that (on Intel x86) the local variables are stored from higher to lower address as they go in the code. So it's clear, that this code:
int i = 0;
char buffer[4];
strcpy(buffer, "aaaaaaaaaaaaaaa");
printf("%d", i);
produces something like this:
1633771873
The i variable was overwritten by the overflowed buffer.
However, if I swap the first two lines:
char buffer[4];
int i = 0;
strcpy(buffer, "aaaaaaaaaaaaaaa");
printf("%d", i);
the output is absolutely same.
How is it possible? The i's address is lower than the buffer's one and so an overflow of the buffer should overwrite other data, but not i. Or am I missing something?
There is no rule about the order of local variables, so the compiler is generally free to allocate them the way it likes. But on the other hand there are many strategies that a compiler will use to reduce the possibility that could happen what you are voluntarily trying to do.
One of those safety enhancement would be to allocate a buffer always far from other scalar variables because an array can be addressed out of bounds and be more incline to bloat adjacent variables. Another trick is to add some trap empty space after arrays to create a kind of isolation for the bounds problem.
Anyway you can use the debugger to have a look to the assembly for confirmation of variables positioning.
If you want to look at how the local variables are allocated by the compiler try compiling with gcc -S which will output the assembly code. On the assembly code you can see how the compiler has chosen to order the variables.
One thing to keep in mind in how the compiler chooses to order local variables is that each char only needs to align by 1 (which means that it can start at any byte of memory), on the other hand the int has to align by 4 (which means that it can only start on a byte evenly divisible by 4), so depending on the alignment the compiler has it's own logic on how to avoid having empty bytes of data which means that it often groups together variables of similar type in a certain order. So even if you define them like this:
int a;
char c;
int b;
char d;
It is likely that the compiler has grouped together the ints and chars in memory so the memory going from low memory on top to high memory on bottom might look something like:
low memory
| | | char d | char c|
| int b |
| int a |
high memory
each block of || represents one byte and an entire line represents 4 bytes.
Try messing around with the assembly code sometime it is pretty interesting.
Just trying to understand how to address a single character in an array of strings. Also, this of course will allow me to understand pointers to pointers subscripting in general.
If I have char **a and I want to reach the 3rd character of the 2nd string, does this work: **((a+1)+2)? Seems like it should...
Almost, but not quite. The correct answer is:
*((*(a+1))+2)
because you need to first de-reference to one of the actual string pointers and then you to de-reference that selected string pointer down to the desired character. (Note that I added extra parenthesis for clarity in the order of operations there).
Alternatively, this expression:
a[1][2]
will also work!....and perhaps would be preferred because the intent of what you are trying to do is more self evident and the notation itself is more succinct. This form may not be immediately obvious to people new to the language, but understand that the reason the array notation works is because in C, an array indexing operation is really just shorthand for the equivalent pointer operation. ie: *(a+x) is same as a[x]. So, by extending that logic to the original question, there are two separate pointer de-referencing operations cascaded together whereby the expression a[x][y] is equivalent to the general form of *((*(a+x))+y).
You don't have to use pointers.
int main(int argc, char **argv){
printf("The third character of
argv[1] is [%c].\n", argv[1][2]);
}
Then:
$ ./main hello The third character of
argv[1] is [l].
That's a one and an l.
You could use pointers if you want...
*(argv[1] +2)
or even
*((*(a+1))+2)
As someone pointed out above.
This is because array names are pointers.
Theres a brilliant C programming explanation in the book Hacking the art of exploitation 2nd Edition by Jon Erickson which discusses pointers, strings, worth a mention for the programming explanation section alone https://leaksource.files.wordpress.com/2014/08/hacking-the-art-of-exploitation.pdf.
Although the question has already been answered, someone else wanting to know more may find the following highlights from Ericksons book useful to understand some of the structure behind the question.
Headers
Examples of header files available for variable manipulation you will probably use.
stdio.h - http://www.cplusplus.com/reference/cstdio/
stdlib.h - http://www.cplusplus.com/reference/cstdlib/
string.h - http://www.cplusplus.com/reference/cstring/
limits.h - http://www.cplusplus.com/reference/climits/
Functions
Examples of general purpose functions you will probably use.
malloc() - http://www.cplusplus.com/reference/cstdlib/malloc/
calloc() - http://www.cplusplus.com/reference/cstdlib/calloc/
strcpy() - http://www.cplusplus.com/reference/cstring/strcpy/
Memory
"A compiled program’s memory is divided into five segments: text, data, bss, heap, and stack. Each segment represents a special portion of memory that is set aside for a certain purpose. The text segment is also sometimes called the code segment. This is where the assembled machine language instructions of the program are located".
"The execution of instructions in this segment is nonlinear, thanks to the aforementioned high-level control structures and functions, which compile
into branch, jump, and call instructions in assembly language. As a program
executes, the EIP is set to the first instruction in the text segment. The
processor then follows an execution loop that does the following:"
"1. Reads the instruction that EIP is pointing to"
"2. Adds the byte length of the instruction to EIP"
"3. Executes the instruction that was read in step 1"
"4. Goes back to step 1"
"Sometimes the instruction will be a jump or a call instruction, which
changes the EIP to a different address of memory. The processor doesn’t
care about the change, because it’s expecting the execution to be nonlinear
anyway. If EIP is changed in step 3, the processor will just go back to step 1 and read the instruction found at the address of whatever EIP was changed to".
"Write permission is disabled in the text segment, as it is not used to store variables, only code. This prevents people from actually modifying the program code; any attempt to write to this segment of memory will cause the program to alert the user that something bad happened, and the program
will be killed. Another advantage of this segment being read-only is that it
can be shared among different copies of the program, allowing multiple
executions of the program at the same time without any problems. It should
also be noted that this memory segment has a fixed size, since nothing ever
changes in it".
"The data and bss segments are used to store global and static program
variables. The data segment is filled with the initialized global and static variables, while the bss segment is filled with their uninitialized counterparts. Although these segments are writable, they also have a fixed size. Remember that global variables persist, despite the functional context (like the variable j in the previous examples). Both global and static variables are able to persist because they are stored in their own memory segments".
"The heap segment is a segment of memory a programmer can directly
control. Blocks of memory in this segment can be allocated and used for
whatever the programmer might need. One notable point about the heap
segment is that it isn’t of fixed size, so it can grow larger or smaller as needed".
"All of the memory within the heap is managed by allocator and deallocator algorithms, which respectively reserve a region of memory in the heap for use and remove reservations to allow that portion of memory to be reused for later reservations. The heap will grow and shrink depending on how
much memory is reserved for use. This means a programmer using the heap
allocation functions can reserve and free memory on the fly. The growth of
the heap moves downward toward higher memory addresses".
"The stack segment also has variable size and is used as a temporary scratch pad to store local function variables and context during function calls. This is what GDB’s backtrace command looks at. When a program calls a function, that function will have its own set of passed variables, and the function’s code will be at a different memory location in the text (or code) segment. Since the context and the EIP must change when a function is called, the stack is used to remember all of the passed variables, the location the EIP should return to after the function is finished, and all the local variables used by that function. All of this information is stored together on the stack in what is collectively called a stack frame. The stack contains many stack frames".
"In general computer science terms, a stack is an abstract data structure that is used frequently. It has first-in, last-out (FILO) ordering
, which means the first item that is put into a stack is the last item to come out of it. Think of it as putting beads on a piece of string that has a knot on one end—you can’t get the first bead off until you have removed all the other beads. When an item is placed into a stack, it’s known as pushing, and when an item is removed from a stack, it’s called popping".
"As the name implies, the stack segment of memory is, in fact, a stack data structure, which contains stack frames. The ESP register is used to keep track of the address of the end of the stack, which is constantly changing as items are pushed into and popped off of it. Since this is very dynamic behavior, it makes sense that the stack is also not of a fixed size. Opposite to the dynamic growth of the heap, as the stack change
s in size, it grows upward in a visual listing of memory, toward lower memory addresses".
"The FILO nature of a stack might seem odd, but since the stack is used
to store context, it’s very useful. When a function is called, several things are pushed to the stack together in a stack frame. The EBP register—sometimes called the frame pointer (FP) or local base (LB) pointer
—is used to reference local function variables in the current stack frame. Each stack frame contains the parameters to the function, its local variables, and two pointers that are necessary to put things back the way they were: the saved frame pointer (SFP) and the return address. The
SFP is used to restore EBP to its previous value, and the return address
is used to restore EIP to the next instruction found after the function call. This restores the functional context of the previous stack
frame".
Strings
"In C, an array is simply a list of n elements of a specific data type. A 20-character array is simply 20 adjacent characters located in memory. Arrays are also referred to as buffers".
#include <stdio.h>
int main()
{
char str_a[20];
str_a[0] = 'H';
str_a[1] = 'e';
str_a[2] = 'l';
str_a[3] = 'l';
str_a[4] = 'o';
str_a[5] = ',';
str_a[6] = ' ';
str_a[7] = 'w';
str_a[8] = 'o';
str_a[9] = 'r';
str_a[10] = 'l';
str_a[11] = 'd';
str_a[12] = '!';
str_a[13] = '\n';
str_a[14] = 0;
printf(str_a);
}
"In the preceding program, a 20-element character array is defined as
str_a, and each element of the array is written to, one by one. Notice that the number begins at 0, as opposed to 1. Also notice that the last character is a 0".
"(This is also called a null byte.) The character array was defined, so 20 bytes are allocated for it, but only 12 of these bytes are actually used. The null byte Programming at the end is used as a delimiter character to tell any function that is dealing with the string to stop operations right there. The remaining extra bytes are just garbage and will be ignored. If a null byte is inserted in the fifth element of the character array, only the characters Hello would be printed by the printf() function".
"Since setting each character in a character array is painstaking and strings are used fairly often, a set of standard functions was created for string manipulation. For example, the strcpy() function will copy a string from a source to a destination, iterating through the source string and copying each byte to the destination (and stopping after it copies the null termination byte)".
"The order of the functions arguments is similar to Intel assembly syntax destination first and then source. The char_array.c program can be rewritten using strcpy() to accomplish the same thing using the string library. The next version of the char_array program shown below includes string.h since it uses a string function".
#include <stdio.h>
#include <string.h>
int main()
{
char str_a[20];
strcpy(str_a, "Hello, world!\n");
printf(str_a);
}
Find more information on C strings
http://www.cs.uic.edu/~jbell/CourseNotes/C_Programming/CharacterStrings.html
http://www.tutorialspoint.com/cprogramming/c_strings.htm
Pointers
"The EIP register is a pointer that “points” to the current instruction during a programs execution by containing its memory address. The idea of pointers is used in C, also. Since the physical memory cannot actually be moved, the information in it must be copied. It can be very computationally expensive to copy large chunks of memory to be used by different functions or in different places. This is also expensive from a memory standpoint, since space for the new destination copy must be saved or allocated before the source can be copied. Pointers are a solution to this problem. Instead of copying a large block of memory, it is much simpler to pass around the address of the beginning of that block of memory".
"Pointers in C can be defined and used like any other variable type. Since memory on the x86 architecture uses 32-bit addressing, pointers are also 32 bits in size (4 bytes). Pointers are defined by prepending an asterisk (*) to the variable name. Instead of defining a variable of that type, a pointer is defined as something that points to data of that type. The pointer.c program is an example of a pointer being used with the char data type, which is only 1byte in size".
#include <stdio.h>
#include <string.h>
int main()
{
char str_a[20]; // A 20-element character array
char *pointer; // A pointer, meant for a character array
char *pointer2; // And yet another one
strcpy(str_a, "Hello, world!\n");
pointer = str_a; // Set the first pointer to the start of the array.
printf(pointer);
pointer2 = pointer + 2; // Set the second one 2 bytes further in.
printf(pointer2); // Print it.
strcpy(pointer2, "y you guys!\n"); // Copy into that spot.
printf(pointer); // Print again.
}
"As the comments in the code indicate, the first pointer is set at the beginning of the character array. When the character array is referenced like this, it is actually a pointer itself. This is how this buffer was passed as a pointer to the printf() and strcpy() functions earlier. The second pointer is set to the first pointers address plus two, and then some things are printed (shown in the output below)".
reader#hacking:~/booksrc $ gcc -o pointer pointer.c
reader#hacking:~/booksrc $ ./pointer
Hello, world!
llo, world!
Hey you guys!
reader#hacking:~/booksrc $
"The address-of operator is often used in conjunction with pointers, since pointers contain memory addresses. The addressof.c program demonstrates
the address-of operator being used to put the address of an integer variable
into a pointer. This line is shown in bold below".
#include <stdio.h>
int main()
{
int int_var = 5;
int *int_ptr;
int_ptr = &int_var; // put the address of int_var into int_ptr
}
"An additional unary operator called the dereference operator exists for use with pointers. This operator will return the data found in the address the pointer is pointing to, instead of the address itself. It takes the form of an asterisk in front of the variable name, similar to the declaration of a pointer. Once again, the dereference operator exists both in GDB and in C".
"A few additions to the addressof.c code (shown in addressof2.c) will
demonstrate all of these concepts. The added printf() functions use format
parameters, which I’ll explain in the next section. For now, just focus on the programs output".
#include <stdio.h>
int main()
{
int int_var = 5;
int *int_ptr;
int_ptr = &int_var; // Put the address of int_var into int_ptr.
printf("int_ptr = 0x%08x\n", int_ptr);
printf("&int_ptr = 0x%08x\n", &int_ptr);
printf("*int_ptr = 0x%08x\n\n", *int_ptr);
printf("int_var is located at 0x%08x and contains %d\n", &int_var, int_var);
printf("int_ptr is located at 0x%08x, contains 0x%08x, and points to %d\n\n", &int_ptr, int_ptr, *int_ptr);
}
"When the unary operators are used with pointers, the address-of operator can be thought of as moving backward, while the dereference operator moves forward in the direction the pointer is pointing".
Find out more about Pointers & memory allocation
Professor Dan Hirschberg, Computer Science Department, University of California on computer memory https://www.ics.uci.edu/~dan/class/165/notes/memory.html
http://cslibrary.stanford.edu/106/
http://www.programiz.com/c-programming/c-dynamic-memory-allocation
Arrays
Theres a simple tutorial on multi-dimensional arrays by a chap named Alex Allain available here http://www.cprogramming.com/tutorial/c/lesson8.html
Theres information on arrays by a chap named Todd A Gibson available here http://www.augustcouncil.com/~tgibson/tutorial/arr.html
Iterate an Array
#include <stdio.h>
int main()
{
int i;
char char_array[5] = {'a', 'b', 'c', 'd', 'e'};
int int_array[5] = {1, 2, 3, 4, 5};
char *char_pointer;
int *int_pointer;
char_pointer = char_array;
int_pointer = int_array;
for(i=0; i < 5; i++) { // Iterate through the int array with the int_pointer.
printf("[integer pointer] points to %p, which contains the integer %d\n", int_pointer, *int_pointer);
int_pointer = int_pointer + 1;
}
for(i=0; i < 5; i++) { // Iterate through the char array with the char_pointer.
printf("[char pointer] points to %p, which contains the char '%c'\n", char_pointer, *char_pointer);
char_pointer = char_pointer + 1;
}
}
Linked Lists vs Arrays
Arrays are not the only option available, information on Linked List.
http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_linklist.aspx
Conclusion
This information was written simply to pass on some of what I have read throughout my research on the topic that might help others.
Iirc, a string is actually an array of chars, so this should work:
a[1][2]
Quote from the wikipedia article on C pointers -
In C, array indexing is formally defined in terms of pointer arithmetic; that is,
the language specification requires that array[i] be equivalent to *(array + i). Thus in C, arrays can be thought of as pointers to consecutive areas of memory (with no gaps),
and the syntax for accessing arrays is identical for that which can be used to dereference
pointers. For example, an array can be declared and used in the following manner:
int array[5]; /* Declares 5 contiguous (per Plauger Standard C 1992) integers */
int *ptr = array; /* Arrays can be used as pointers */
ptr[0] = 1; /* Pointers can be indexed with array syntax */
*(array + 1) = 2; /* Arrays can be dereferenced with pointer syntax */
So, in response to your question - yes, pointers to pointers can be used as an array without any kind of other declaration at all!
Try a[1][2]. Or *(*(a+1)+2).
Basically, array references are syntactic sugar for pointer dereferencing. a[2] is the same as a+2, and also the same as 2[a] (if you really want unreadable code). An array of strings is the same as a double pointer. So you can extract the second string using either a[1] or *(a+1). You can then find the third character in that string (call it 'b' for now) with either b[2] or *(b + 2). Substituting the original second string for 'b', we end up with either a[1][2] or *(*(a+1)+2).