Unit | Number of Bytes |
kB kilobyte | 210 = 1024 |
MB megabyte | 220 = 1024 * 1024 |
GB giga byte | 230 = 1024 * 1024 * 1024 |
TB terabyte | 240 = 1024 * 1024 * 1024 * 1024 |
Address (hex) | Function |
0 - 1F | 32 Registers |
20 - 5F | 64 I/O Regusters |
60 - 1FF | 416 External I/O Registers |
200 - 21FF | Internal SRAM (8 kB bytes) |
2200 - FFFF | External SRAM (55.5 kB) |
// put a single byte into our data memory |
// if you remove the initialization (= 1) the |
// location changes from 0x200 to 0x212 |
char var_1 = 1; |
|
void setup() { |
Serial.begin(9600); |
|
// print out its address, which is 16 bits unsigned |
Serial.println( (uint16_t) &var_1, HEX); |
} |
|
void loop() { |
} |
&
operator (a mnemonic for the address) in Serial.println
. It is used to obtain the address of the variable it is being applied to. var_1
, then it starts at 0x212. Something else must be at 0x200. Variables are rearranged so that those requiring initialization are before those that do not. // put 2 char variables and 2 int variables into our data memory |
char var_1 = 1; |
uint8_t var_2 = 2; |
int var_3 = 3; |
int16_t var_4 = 4; |
|
void setup() { |
Serial.begin(9600); |
Serial.println("Mem02"); |
|
// print out its address, which is 16 bits unsigned |
Serial.println( (uint16_t) &var_1, HEX); |
Serial.println( (uint16_t) &var_2, HEX); |
Serial.println( (uint16_t) &var_3, HEX); |
Serial.println( (uint16_t) &var_4, HEX); |
} |
|
void loop() { |
} |
var_1 | 0x206 |
var_2 | 0x207 |
var_3 | 0x208 |
var_4 | 0x20A |
var_3
is an int, it takes 2 bytes. So var_4
starts 2 bytes later in memory than var_3
. We low to high memory allocation with the global variables in this program. // show how the sizeof operator works |
char var_1 = 1; |
int16_t var_2 = 2; |
int32_t var_3 = 3; |
void* var_4; // this is an address |
|
void setup() { |
Serial.begin(9600); |
Serial.println("Mem03"); |
|
// print out its address, which is 16 bits unsigned |
Serial.print( (uint16_t) &var_1, HEX); |
Serial.print( " " ); |
Serial.println( sizeof(var_1), HEX); |
|
Serial.print( (uint16_t) &var_2, HEX); |
Serial.print( " " ); |
Serial.println( sizeof(var_2), HEX); |
|
Serial.print( (uint16_t) &var_3, HEX); |
Serial.print( " " ); |
Serial.println( sizeof(var_3), HEX); |
|
Serial.print( (uint16_t) &var_4, HEX); |
Serial.print( " " ); |
Serial.println( sizeof(var_4), HEX); |
} |
|
void loop() { |
} |
000021ff | W | __stack |
00800200 | D | __data_start |
00800208 | D | var_1 |
00800209 | D | var_2 |
0080020b | D | var_3 |
0080020f | V | _ZTV14HardwareSerial |
00800222 | B | __bss_start |
00800222 | B | var_4 |
00800222 | D | __data_end |
00800222 | D | _edata |
00800224 | B | rx_buffer |
008002a8 | B | rx_buffer1 |
0080032c | B | rx_buffer2 |
008003b0 | B | rx_buffer3 |
00800434 | B | Serial |
00800447 | B | Serial1 |
0080045a | B | Serial2 |
0080046d | B | Serial3 |
00800480 | B | timer0_overflow_count |
00800484 | B | timer0_millis |
00800488 | b | timer0_fract |
00800489 | B | __bss_end |
00800489 | N | __heap_start |
00800489 | N | _end |
_ZTV14HardwareSerial
. v
is given by &v
p
, *p
dereferences p
to get to the actual variable pointed to by p
*&v
is the same as v
. The expression &*v
makes sense only if v
is a pointer, and in that case &*v
is also the same as v
.
v
is typeof(v)
, a gcc extension to the language.
v
is sizeof(v)
. You can also use a type as the argument, such as sizeof(uint32_t)
. v
is at location &v + sizeof(v)
where the arithmetic is done in bytes. (See address arithmetic in §11 where we talk about arrays and structs.) v
is at location &v - 1
using bytes arithmetic. If you need to put a new variable w
onto the stack, then its byte address will be &v - sizeof(w)
(using bytes arithmetic).
void *
. You cannot dereference a generic pointer unless you use a type conversion or typecast. A typecast is indicated by putting the desired conversion in front of the expression. For example, uint8_t *p1;
p1 = (uint8_t *) p;
Turns p
into a pointer to a uint8_t
(i.e. a byte) no matter what p
is, and assigns the resulting value to p1
. This is, understandably, a dangerous thing to do.
// how are multibyte integers placed in memory? |
|
// some easily identifiable byte patterns |
uint16_t X = 0x1122; |
|
void setup() { |
uint8_t *p; // a pointer to a byte |
|
Serial.begin(9600); |
Serial.println("Endian Test"); |
|
// print the parts of X |
Serial.print("X is 0x"); Serial.println(X, HEX); |
|
p = (uint8_t *) &X; |
|
Serial.print("p is 0x"); Serial.println( (uint16_t) p, HEX); |
Serial.print("*p is 0x"); Serial.println( *p, HEX); |
Serial.print("*(p+1) is 0x"); Serial.println( *(p+1), HEX); |
|
if ( *p == 0x11 ) { |
Serial.println("This is a big-endian machine."); |
} |
else { |
Serial.println("This is a little-endian machine."); |
} |
} |
|
void loop() { |
} |
Endian Test
X is 0x1122
p is 0x271
*p is 0x22
*(p+1) is 0x11
This is a little-endian machine.
{
uint16_t x;
{
uint32_t y;
}
}
but not this: {
uint16_t x;
{
uint32_t y;
}
}
This means that any inner block that allocates a variable is always left before any enclosing block, so that the more recently allocated variables are closer to the top of the stack. #include <Arduino.h> |
#include <mem_syms.h> |
|
// return the sum of the elements of an array |
int16_t sum(uint16_t len, int16_t A[]) { |
int16_t result = 0; |
for (uint16_t i=0; i < len; i++) { |
result += A[i]; |
} |
return result; |
} |
|
void do_stuff(uint16_t len, int16_t odd_even_data[]) { |
Serial.print("Stack before extracting odd: "); |
Serial.println(STACK_SIZE); |
{ |
// create a temporary working array to collect odd elements of data |
int16_t odd_data[len]; |
uint16_t odd_len = 0; |
int16_t odd_sum; |
|
for (uint16_t i=0; i < len; i++) { |
if ( odd_even_data[i] & 1 == 1 ) { |
odd_data[odd_len] = odd_even_data[i]; |
odd_len++; |
} |
} |
|
Serial.print("Stack after extracting odd: "); |
Serial.println(STACK_SIZE); |
|
odd_sum = sum(odd_len, odd_data); |
Serial.print("Odd sum is "); Serial.println(odd_sum); |
} |
|
Serial.print("Stack after leaving block: "); |
Serial.println(STACK_SIZE); |
} |
|
void setup() { |
|
uint16_t i; |
const uint16_t n_elements = 64; |
int16_t data[n_elements]; |
|
Serial.begin(9600); |
|
for (i=0; i < n_elements; i++) { |
data[i] = i; |
} |
|
do_stuff(n_elements, data); |
} |
|
void loop() { |
} |
|
data
(64 * 2 bytes). Note that the storage for the small items, odd_len
and odd_sum
is probably in registers (for speed) and not on the stack. The compiler optimizations make it difficult to predict exactly where certain variables will be located. Stack before extracting odd: 157
Stack after extracting odd: 285
Odd sum is 1024
Stack after leaving block: 157
We use this feature to allocate a temporary array S
in one of our versions of merge sort in the section on sorting §12. x = f(a, b, c);
the following happens: a
b
and c
are pushed onto the stack, x
is obtained from the stack int32_t gcd(int32_t a, int32_t b) { |
if (b == 0) { |
return a; |
} |
if (b == 1) { |
return 1; |
} |
return gcd(b, a % b); |
} |
|
void setup() { |
Serial.begin(9600); |
Serial.println( gcd(36, 24) ); |
} |
|
void loop() { |
} |
do_something(x, y, z);
does not pass the actual variables the procedure do_somthing
. Instead only the current values of the variables are passed. The procedure has no idea where these values came from. This is called call by value. #include <Arduino.h> |
|
// this does not modify the original arguments, since |
// x and y are copies of the arguments |
void swap_int16_t_bad(int16_t x, int16_t y) { |
int16_t tmp = x; |
x = y; |
y = tmp; |
} |
|
// this does not modify the original arguments either, |
// since x_p and y_p are copies of the arguments. However, |
// they are pointers to the variables that we want to swap |
// values for. |
void swap_int16_t(int16_t *x_p, int16_t *y_p) { |
int16_t tmp = *x_p; |
*x_p = *y_p; |
*y_p = tmp; |
} |
|
void setup() { |
Serial.begin(9600); |
|
int16_t a = 2; |
int16_t b = 3; |
|
Serial.print("a b are "); |
Serial.print(a); |
Serial.print(" "); |
Serial.println(b); |
|
// swap a and b with inline code |
{ |
int16_t tmp = a; |
a = b; |
b = tmp; |
} |
|
Serial.print("a b are "); |
Serial.print(a); |
Serial.print(" "); |
Serial.println(b); |
|
// swap that doesn't work because only the values in |
// a and b are passed |
swap_int16_t_bad(a, b); |
|
Serial.print("a b are "); |
Serial.print(a); |
Serial.print(" "); |
Serial.println(b); |
|
// swap that does work because we pass the address of |
// the variables whose values need to be changed. |
swap_int16_t(&a, &b); |
|
Serial.print("a b are "); |
Serial.print(a); |
Serial.print(" "); |
Serial.println(b); |
} |
|
void loop() { |
} |
a b are 2 3
a b are 3 2
a b are 3 2
a b are 2 3
which shows that unless you pass the address of a variable v
to a procedure you will not be able to alter the contents of v
. \0
) then the variable associated with the array is already a pointer. This still means that the procedure cannot modify the argument (the pointer), but it can of course modify the array pointed to by the argument. #include <Arduino.h> |
|
// swap the contents of array x and y, both of length len |
void swap_int16_t_array(int16_t x[], int16_t y[], uint16_t len) { |
for (uint16_t i=0; i < len; i++) { |
int16_t tmp = x[i]; |
x[i] = y[i]; |
y[i] = tmp; |
} |
} |
|
void print_array(int16_t x[], uint16_t len) { |
for (uint16_t i=0; i < len; i++) { |
Serial.print(" "); |
Serial.print(x[i]); |
} |
} |
|
void setup() { |
Serial.begin(9600); |
|
int16_t len = 2; |
int16_t a[2] = {11, 22}; |
int16_t b[2] = {33, 44}; |
|
Serial.print("a"); print_array(a, len); Serial.println(); |
Serial.print("b"); print_array(b, len); Serial.println(); |
|
swap_int16_t_array(a, b, len); |
Serial.println("After swap"); |
Serial.print("a"); print_array(a, len); Serial.println(); |
Serial.print("b"); print_array(b, len); Serial.println(); |
} |
|
void loop() { |
} |
a 11 22
b 33 44
After swap
a 33 44
b 11 22
// given array x of length len
// position i, 0 <= i < len
// a pointer to result r
// set r to the value of x[i]
void extract(int16_t x, uint16_t len, uint16_t i, int16_t *r)
mem_syms.h
contains a number of macros that let you inspect the current state of the stack and heap to monitor your memory usage. malloc
, and released by using free
. mem_syms.h
We use macros instead of functions because we don't want the inspection to affect the amount of memory in use. Calling a function would allocate memory on the stack. mem_syms.h
, you can copy it into the directory with the file that you wish to include those macros. In your main file, you will also need to tell the complier to include the contents of the file. mem_syms.h
. This file is also in the UAUtils lib. /* |
|
If you have the libc 1.6.4 libraries then free space at the end of the |
heap is NOT released. |
|
With the libc 1.7.1 libraries with the fixed malloc, then free space |
at the end of the heap is released so that the space is available |
to the stack or the heap. |
|
AVAIL_MEM is a macro to tell you how much available free memory is left |
between the top of stack and end of heap. |
|
STACK_TOP is a macro to tell you the current address of the top of |
the stack. There is data in this location ?? |
|
STACK_BOTTOM is a macro to tell you the address of the bottom of the |
stack. It should normally be constant. |
|
STACK_SIZE is a macro to tell you the current size of the stack |
|
HEAP_START is a macro to provide the address of the first location in |
the heap. |
|
HEAP_END is macro to provide the address of the current last location in |
in the heap. This will increase as memory is allocated, and decrease if |
the end of the heap consists of free memory and can be shrunk. |
|
HEAP_SIZE is a macro to tell you the current size of the heap, but it |
includes chunks that are free. |
|
There should be a routine to detect the presence of the old or new malloc. |
|
*/ |
|
#ifndef mem_syms_h |
#define mem_syms_h |
|
#include "avr/io.h" |
|
/* these symbols get us to the details of the stack and heap */ |
|
// the address of the first byte of the stack, the last byte of onchip SRAM |
#define STACK_BOTTOM ( (char*)RAMEND ) |
|
// the first free location at the top of the stack |
#define STACK_TOP ((char *)AVR_STACK_POINTER_REG) |
|
// the amount of stack used. i.e. from bottom to top-1 |
#define STACK_SIZE (STACK_BOTTOM - STACK_TOP) |
|
#define AVAIL_MEM (STACK_TOP - HEAP_END) |
|
// the address of the first location of the heap |
#define HEAP_START __malloc_heap_start |
|
// the address of the next free location after the end of the heap |
// __brkval is 0 until the first malloc |
#define HEAP_END (__brkval ? __brkval : __malloc_heap_start) |
|
// the amount of space taken up by the heap, including free chunks |
// inside the heap |
#define HEAP_SIZE (HEAP_END - HEAP_START) |
|
extern char*__brkval; |
|
#endif |
// show temporary variable allocation |
// remove var = initialization and location changes from 0x200 to 0x212 |
|
void setup() { |
Serial.begin(9600); |
|
char var_1; |
int16_t var_2; |
int16_t var_3; |
|
Serial.println("MEM04"); |
|
Serial.println( (uint16_t) &var_1, HEX); |
Serial.println( (uint16_t) &var_2, HEX); |
Serial.println( (uint16_t) &var_3, HEX); |
Serial.println( (uint16_t) &var_3 + sizeof(var_3), HEX); |
|
Serial.println("i, j"); |
for (int16_t i = 0; i < 2; i++) { |
char j = i; |
Serial.println( (uint16_t) &i, HEX); |
Serial.println( (uint16_t) &j, HEX); |
} |
|
Serial.println("k, l"); |
for (int16_t k = 0; k < 2; k++) { |
int l; |
Serial.println( (uint16_t) &k, HEX); |
l = k + k; |
Serial.println( (uint16_t) &l, HEX); |
} |
} |
|
void loop() { |
} |
i
and j
are also on the stack, but in non-adjacent positions. // show temporary variable allocation |
#include "mem_syms.h" |
// remove initialization and location changes from 0x200 to 0x212 |
|
// format for printing addresses, sometimes HEX, sometimes DEC |
#define ADDR DEC |
#define PRINT_ADDR(x) Serial.println( (int) x, ADDR) |
|
void setup() { |
Serial.begin(9600); |
|
Serial.println("MEM05"); |
|
Serial.println("Stack bot, top, size"); |
Serial.println( (int) STACK_BOTTOM, ADDR); |
Serial.println( (int) STACK_TOP, ADDR); |
Serial.println( (int) STACK_SIZE, ADDR); |
|
Serial.println("Avail"); |
Serial.println( (int) AVAIL_MEM, ADDR); |
|
Serial.println("Heap start, end, size"); |
Serial.println( (int) HEAP_START, ADDR); |
Serial.println( (int) HEAP_END, ADDR); |
Serial.println( (int) HEAP_SIZE, ADDR); |
char *c = (char *) malloc(10); |
Serial.println( (int) HEAP_END, ADDR); |
Serial.println( (int) HEAP_SIZE, ADDR); |
|
|
Serial.println( (int) STACK_TOP, ADDR); |
|
Serial.println("Avail"); |
Serial.println( (int) AVAIL_MEM, ADDR); |
|
{ |
char x; |
Serial.println( (int) &x, ADDR); |
Serial.println( (int) STACK_TOP, ADDR); |
} |
Serial.println( (int) STACK_TOP, ADDR); |
} |
|
void loop() { |
} |
// What is used by a function call? |
|
#include "mem_syms.h" |
// remove initialization and location changes from 0x200 to 0x212 |
|
// format for printing addresses, sometimes HEX, sometimes DEC |
#define ADDR DEC |
#define PRINT_ADDR(x) Serial.println( (int) x, ADDR) |
|
char* stack_before; |
char* stack_after; |
int stack_used_by_call; |
|
void fn() { |
int x = 42; |
stack_after = STACK_TOP; |
// do something |
Serial.print("fn "); |
Serial.println(x, DEC); |
} |
|
void setup() { |
Serial.begin(9600); |
|
Serial.println("MEM06"); |
|
Serial.println("Stack bot, top, size"); |
Serial.println( (int) STACK_BOTTOM, ADDR); |
Serial.println( (int) STACK_TOP, ADDR); |
Serial.println( (int) STACK_SIZE, ADDR); |
|
stack_before = STACK_TOP; |
fn(); |
|
|
stack_used_by_call = (int) stack_before - (int) stack_after; |
|
Serial.print("Stack used "); |
Serial.println(stack_used_by_call, ADDR); |
|
Serial.println("Stack bot, top, size"); |
Serial.println( (int) STACK_BOTTOM, ADDR); |
Serial.println( (int) STACK_TOP, ADDR); |
Serial.println( (int) STACK_SIZE, ADDR); |
|
} |
|
void loop() { |
} |
// What is used by a function call? |
|
#include "mem_syms.h" |
#define ADDR DEC |
|
unsigned int gcd(unsigned int a, unsigned int b) { |
unsigned int result; |
|
Serial.print("Stack size: "); |
Serial.println( (int) STACK_SIZE, ADDR); |
|
if ( a == 0 ) { |
result = b; |
} |
else if ( a == 1 ) { |
result = 1; |
} |
else { |
result = gcd(b, a % b); |
} |
|
// comment out the next two lines and observe the change in stack size |
Serial.print(a, DEC); Serial.print(" "); |
Serial.print(b, DEC); Serial.println(" "); |
return result; |
} |
|
|
void setup() { |
unsigned int g; |
Serial.begin(9600); |
|
Serial.println("MEM07"); |
|
Serial.print("Avail mem:"); Serial.println(AVAIL_MEM, DEC); |
|
g = gcd(8820, 462); |
Serial.print("gcd is "); |
Serial.println(g, DEC); |
} |
|
void loop() { |
} |
10. Memory Tangible Computing / Version 3.20 2013-03-25 |