Consider this python code. What's going on? Run in pythontutor.com.
a = [1, 2, 3]
b = [1, 2, 3]
print("a is b ? ", a is b)
print("id(a) ", hex(id(a)))
print("id(b) ", hex(id(b)))
c = b
print("id(c) ", hex(id(c)))
print("c is b ? ", c is b)
Python doesn't use these "identities" of variables, except to tell whether or not two variables refer to the same address in memory.
But C uses address quite a lot. Here are two new operators that you may be unfamiliar with : * and & .
// --- ptrs .c ---
#include <stdio.h>
int main(){
int a = 1234;
int b = 5678;
int* a_p = &a; // pointer to a
int* b_p = &b; // pointer to b
int** a_pp = &a_p; // pointer to pointer to a
int** b_pp = &b_p; // pointer to pointer to b
printf("--- address and the values within them --- \n");
printf("&a, a : %12p %14d \n", &a, a);
printf("&b, b : %12p %14d \n", &b, b);
printf("&a_p, a_p : %12p %12p \n", &a_p, a_p);
printf("&b_p, b_p : %12p %12p \n", &b_p, b_p);
printf("&a_pp, a_pp : %12p %12p \n", &a_pp, a_pp);
printf("&b_pp, b_pp : %12p %12p \n", &b_pp, b_pp);
printf("--- follow the pointer --- \n");
printf("*a_p : %d \n", *a_p);
printf("**a_pp : %d \n", **a_pp);
printf("--- sizes of things (in bytes) --- \n");
printf("sizeof(int) : %lu \n", sizeof(int));
printf("sizeof(int*) : %lu \n", sizeof(int*));
printf("sizeof(int**) : %lu \n", sizeof(int**));
return 0;
}
Compiling and running that on jupyter.bennington gives
jim@jupyter:~$ gcc ptrs.c -o ptrs
jim@jupyter:~$ ./ptrs
--- address and the values within them ---
&a, a : 0x7fff10f261e0 1234
&b, b : 0x7fff10f261e4 5678
&a_p, a_p : 0x7fff10f261e8 0x7fff10f261e0
&b_p, b_p : 0x7fff10f261f0 0x7fff10f261e4
&a_pp, a_pp : 0x7fff10f261f8 0x7fff10f261e8
&b_pp, b_pp : 0x7fff10f26200 0x7fff10f261f0
--- follow the pointer ---
*a_p : 1234
**a_pp : 1234
--- sizes of things (in bytes) ---
sizeof(int) : 4
sizeof(int*) : 8
sizeof(int**) : 8
Discuss what this is all about.
The notation is set up so that int* i_ptr;
and int *i_ptr
are both OK, and mean the same
thing: i_ptr is a pointer to (i.e. the address of) an integer.
In C, an array like int numbers[10]
actually means that numbers
is essentially an int*,
a pointer to an int, which is the first element
of the array. So numbers[0]
is also *numbers
,
and numbers[1]
is also *(numbers+1)
.
Consider the following python code.
def foo():
a = [1,2,3]
print("id(a) is ", id(a))
b = [4,5,6]
return a
aa = foo()
print(aa)
print("id(aa) is ", id(aa))
print(b)
Quick quiz: what happened to the memory where [1,2,3] was? what happened to the memory where [4,5,6] was? How does the program know the difference? Was the whole array returned ... or just a reference to it? (Hint: is id(a) the same or different than id(aa)?)
In C, just like in Python, we don't usually copy an array when we return it - instead we return its address. But we also have to make sure that it exists even when the function stops running. (Python does that for you.)
You might try to do this ...
// THIS CODE HAS A SERIOUS BUG IN IT.
int* foo(){
int a[] = {1,2,3};
int b[] = {4,5,6};
return a; // Here "a" is the same as &a[0], address of array.
}
... but you would be cruising for a bruising. The address of where those numbers were is returned, but the memory itself is not kept. In C, each function's local variables are explicitly declared, and all are up for grabs once the function is finished.
If in C you want to have a function that creates and returns a block of memory, you must instead use malloc (i.e. "memory allocate") or a similar system function, which reserves some memory off in a special place for dynamic memory for that process. The call to malloc returns a pointer to that memory.
// THIS IS OK.
int* foo(){
int* a = malloc(3 * sizeof(int)); // space for 3 integers
int i;
for (i=1; i<=3; i++){ a[i] = i; } // fill with 1,2,3
return a;
}
C is a primitive, low level language. You can't even tell how long the array is without sending along its size as another parameter!
Here is the approach that I like in C. We define a new type which is a pointer to to a block of memory (a struct). We also set up functions which allocate one of those blocks. And then we only use those pointers.
A version of this with more commentary is attached.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct _mything *mything;
struct _mything {
int count; // an integer value
char name[32]; // a string of 32 characters.
};
mything new_mything(){
// allocate a mything block of memory; return a pointer to it.
mything newthing = malloc(sizeof(struct _mything));
newthing->count = 0; // initialize its count
strcpy(newthing->name, ""); // copy empty string to it's name.
return newthing;
}
int main(){
mything george = new_mything();
george->count = 33;
strcpy(george->name, "George");
printf("The object has count=%i and name=%s.\n",
george->count, george->name);
return 0;
}
And here are more examples of all this sort of stuff :
last modified | size | ||
c_object_template.c | Wed Mar 02 2022 11:53 pm | 2.6K |