(C) what ways can we manipulate memory length and addresses? - c

im having a tough time proposing this question to alot of people. if anyone here can help me shed some light on this i would greatly appreciate as this has been the ultimate road block for almost three years with my problem.
if you know your arrays and memory than skip the the last paragraph for the big question, but if you read through this it may help you understand why and with what concepts i am struggling to grasp.
so we have an initialized array
int main(){
int x=10;
int arr[x]={3,5,1,9,4,17,2,12,6,8};
to access the 5th element of the array, we would print it out as follows
printf("%d",arr[4]);
now, my first question revolves around this process. the printf function defines that i want to print an integer, and as a directory, i give it the array name/address of first element and specify that i would like the 4th element of the array.
the information i have so far leads me to believe that each integer occupies 4 bytes of memory. usually, in a classroom setting when a professor explains this they draw this on the board
0 1 2 3 4 5 6 7 8 9
[1000][1004][1008][1012][1016][1020][1024][1028][1032][1036]
{ 3 5 1 9 4 17 2 12 6 8 }
now, this is all basic but during my time in learning how computers work ive realized one very important thing, computers are very stupid and that is why we program them, so please bear with me.
question 1: is this an accurate representation of memory? this is how almost everyone on youtube, in classrooms, etc. paints the picture, a sequential list starting at 1000 and increasing by +4, what if i have two arrays of the same size? they cant occupy the same memory addresses so how can i keep track of the where.
question 2: i understand that arr[4]; refers to the value of the 4th position of the array. but this arr[4], what is it? the 4. is this 4 stored in memory somewhere? what data type is this 4? is it a pointer as well? my understanding is that it is an extension of the pointer array, which confuses me. because the array points to the the 1st element in position 0, how does a pointer "4" coexist inside of a pointer? what does the process look like for the computer? im assuming its not a problem because 4 is a pointer argument and can therefore exist within the indicated array pointer.
but the process? is it, go to arr[0] and then count 4 bytes from position 0 4 times? what address does position 0 hold? i know for teaching purposes its visualized as starting at 1000 but is that always the case? why not address 1036 and count 4 up from there? i know i read somewhere that memory addresses compartmentalize storage by making sure if a char (1 byte) is stored in memory next to an int (4 bytes) then there are gaps of memory between to make it all a divisible of 2.
so now on to my final question, which i cant find anything on the interwebz about. can i somehow tell the computer to assign the length of memory from index 0 - n to a variable? maybe im asking in the wrong way, so let me rephrase. is there a data type that defines length of memory and not position? i know we can access the amount of memory a certain variable is taking, but to do that we reference the variable and receive the memory as a result. i want to assign a length of memory to a variable.

is there a data type that defines length of memory and not position?
ALL data types in C do this. Every type defines how many bytes are needed to hold that type and what the individual bits in each byte mean. This is implementation defined, so different compilers and different targets define them differently, but the language gives you the tools to write portable code that will work in a well-defined manner whatever that may be.
sizeof tells you the size of any type in bytes. so sizeof(int) will tell you how large an int is on your target -- generally 4, but some targets use 8 or 2.
CHAR_BIT tells you the size of a byte -- how many bits are in a byte. You don't often need this, but when you do, it is available.

Related

Computer Memory Allocation for Duplicate Inputs

I'm taking Introduction to CS (CS50, Harvard) and we're learning type declaration in C. When we declare a variable and assign a type, the computer's allocating a specific amount of bits/bytes (1 byte for char, 4 bytes for int, 8 bytes for doubles etc...).
For instance, if we declare the string "EMMA", we're using 5 bytes, 1 for each "char" and 1 extra for the \0 null byte.
Well, I was wondering why 2 M's are allocated separate bytes. Can't the computer make use of the chars or integers currently taking space in the memory and refer to that specific slot when it wants to reuse it?
Would love some education on the matter (without getting too deep, as I'm fairly new to the field).
Edit: Fixed some bits into bytes — my bad
1 bit for char, 4 bytes for int, 8 bytes for doubles etc...
These are general values but they depend on the architecture (per this answer, there are even still 9-bit per byte architectures being sold these days).
Can't the computer make use of the chars or integers currently taking space in the memory and refer to that specific slot when it wants to reuse it?
While this idea is certainly feasible in theory, in practice the overhead is way too big for simple data like characters: one character is usually a single byte.
If we were to set up a system in which we allocate memory for the character value and only refer to it from the string, the string would be made of a series of elements which would be used to store which character should be there: in C this would be a pointer (you will encounter them at some point in your course) and is usually either 4 or 8 bytes long (32 or 64 bits). Assuming you use a 32-bit pointer, you would use 24 bytes of memory to store the string in this complex manner instead of 5 bytes using the simpler method (to expand on this answer, you would need even more metadata to be able to properly modify the string during your program's execution).
Your idea of storing a chunk of data and referring to it multiple times does however exist in several cases:
virtual memory (you will encounter this if you go towards OS development), where copy-on-write is used
higher level languages (like C++)
filesystems which implement a copy-on-write feature, like BTRFS
some backup systems (like borg or rsync) which deduplicate the files/chunks they store
Facebook's zstandard compression algorithm, where a dictionnary of small common chunks of data is used to improve compression ratio and speed
In such settings, where lots of data are stored, the relative size of the information required to store the data once and refer to it multiple times while improving copy time is worth the added complexity.
For instance if we declare the string "EMMA", we're using 5 bits
I am sure you are speaking about 5 bytes instead of 5 bits.
Well, I was wondering why 2 M's are allocated separate bits. Can't the
computer make use of the chars or integers currently taking space in
the memory and refer to that specific slot when it wants to reuse it?
A pointer to a "slot" usually occupies 4 or 8 bytes. So there is no sense to spend 8 bytes to point to an object that occupies only one byte
Moreover "EMMA" is a character array that consists from adjacent bytes. So all elements of the array has the same type and correspondingly size.
The compiler can reduce the memory usage by avoiding duplicated string literals. For example it can stores the same string literals as one string literal. This depends on a compiler option.
So if in the program the same string literal occurs for example two times as in these statements
char *s = malloc( sizeof( "EMMA" ) );
strcpy( s, "EMMA" );
then the compiler can store only one copy of the string literal.
The compiler is not supposed to be the code/program but something that does the minimal and it has to perform tasks such that it is easy for programmers to understand and manipulate,in other words it has to be general.
as a programmer you can make your program to save data in the suggested way but it won't be general .
eg- i am making a database for my school and i entered a wrong name and now i want to change the 2nd 'm' in "EMMA",now this would be troublesome if the system worked as suggested by you.
would love to clarify further if needed. :)

How does a C program get information from an array internally?

I'm relatively new to programming, so when someone suggested that building an array of structs (each containing n attributes of a particular "item") was faster than building n arrays of attributes, I found that I didn't know enough about arrays to argue one way or the other.
I read this:
how do arrays work internally in c/c++
and
Basic Arrays Tutorial
But I still don't really understand how a C program retrieves a particular value from an array by index.
It seems pretty clear that data elements of the array are stored adjacent in memory, and that the array name points to the first element.
Are C programs smart enough to do the arithmetic based on data-type and index to figure out the exact memory address of the target data, or does the program have to somehow iterate over every intermediary piece of data before it gets there (as in the linkedlist data structure)?
More fundamentally, if a program asks for a piece of information by its memory address, how does the machine find it?
Let's take a simpler example. Let's say you have a array int test[10] which is stored like this at address 1000:
1|2|3|4|5|6|7|8|9|10
The complier knows that, for example, an int is 4 bytes. The array access formula is this:
baseaddr + sizeof(type) * index
The size of a struct is just the sum of the sizes of its elements plus any padding added by the compiler. So the size of this struct:
struct test {
int i;
char c;
}
Might be 5. It also might not be, because of padding.
As for your last question, very shortly (this is very complicated) the MMU uses the page table to translate the virtual address to a physical address, which is then requested, which if it's in cache, is returned, and otherwise is fetched from main memory.
You wrote:
Are C programs smart enough to do the arithmetic based on data-type and index to figure out the exact memory address of the target data
Yes, that is exactly what they do. They do not iterate over intervening items (and doing so would not help since there are no markers to guide beginning and end of each item).
so here is the whole trick, array elements are adjacent in memory.
when you declare an array for example: int A[10];
the variable A is a pointer to the first element in the array.
now comes the indexing part, whenever you do A[i] it is exactly as if you were doing *(A+i).
the index is just an offset to the beginning address of the array, also keep in mind that in pointer arithmetic the offset is multiplied by the size of the data type of the array.
to get a better understanding of this write a little code, declare an array and print the address of it and then of each element in the array.
and notice how the offset is always the same and equal to the size of the data type of your array on your machine.

What do we have in the bytes beginning at the "Address of a function"?How to know how many bytes to consider?

My brain gets numb just even imagining this.So bear with me if my question is little wordy.So I've sliced my question into parts.
1) What do we have at the at the bits/bytes starting at the address of the function?I mean,at an integer variable's address, we visualize 4 bytes(for 32 bit systems) of 1's and 0's that represent the number in binary form.For a character variable we visualize a single byte with the ASCII value of the character.For a double we visualize 8 bytes accordingly.But what on earth should I visualize at the bytes starting with the address of a function?I know that a call stack is created when a function is invoked,but what about the function itself?At its address do we have the function's expressions,ifs,loops, etc in binary form?Are those bits/bytes representing a function too complicated to visualize by a human unlike say integers?
2) Can we use sizeof for a function?Why or why can't we?If we have no idea how to determine the size allocated to a function, then how do functions have addresses?As if they have addresses,they must have size and since we have pointers to functions, how is it determined by the pointers how many bytes to interpret starting with pointer address?After all we can use those pointers to invoke the functions.
Please be generous with the details.Books and Google hasn't been helpful at all in this regard.
It can be anything at all. It is not required to be anything specific.
No. A function's address is just the entry point. There's no requirement that it, for example, even occupy consecutive memory locations.
Usually, the function address is where the actual machine code for that function begins. There's no reliable way to tell where the function ends. Some platforms might lay out functions as they appear in the source code, one after the other. But other platforms, particularly ones with IPO, won't be nearly as simple.
In most C implementations, a pointer to a function is implemented as an address of the start of the function’s machine code. The bytes at that address are the bytes of the instructions that are executed when the function is called.
In some implementations, a pointer to a function is implemented as an address of data about the function, such as data that contains the address of the machine code and a description of the function’s parameters or register use.
This answer is just for educational purposes, because these details are not part of the C standard and vary between implementations.
1.
I usually visualize the memory pointed to by a function pointer as the assembler mnemonics themselves instead of a stream of bytes. If you're on a architecture with fixed-width instructions, you can visualize it as an array of integers - each encoding a different instruction.
2.
No, you can't. There's some great answers on SO that explain why you can't sizeof() a function but it basically boils down to the fact that code for that function isn't guaranteed to be all together so it's impossible to determine the size. A compiler could emit instructions that jump into another functions if it wanted to (ironically, this is exactly what happens when you call a function or evoke a function pointer ;) ).
It is perfectly possible and valid to have an address of something and not know its size - just look at a void pointer for example. Just as we don't know the size of the data a void pointer points to, we don't know the size of code that a function pointer points to.

What is the purpose of the byte size of the type of a variable if I know the address of the variable?

I am not getting the whole purpose of working with the byte size of a variable by knowing the address of it. For example, let's say I know where an int variable is stored, let's say it is stored in address 0x8C729A09, if I want to get the int stored in that address I can just dereference the address and get the number stored on it.
So, what exactly is the purpose of knowing the byte size of the variable? Why does it matter if the variable has 4 bytes (being int) or 8 bytes if I am able to get the value of the variable by just dereference the address? I am asking this, because I am working on dereferencing some address and I thought that I needed to go through a for loop to get the variable (By knowing the start address, which is the address of the variable, and the size of the variable in bytes) but whenever I do this I am just getting other variables that are also declared.
A little bit of context: I am working on a tool called Pin and getting the addresses of the global variables declared in another program.
The for case looks something like this:
for(address1 = (char *) 0x804A03C, limit = address1 + bytesize; address1 < limit; address1++)
cout << *(address1) << "\n";
Michael Krelin gave a very compact answer but I think I can expand on it a bit more.
In any language, not just C, you need to know the size for a variety of reasons:
This determines the maximum value that can be stored
The memory space an array of those values will take (1000 bytes will get you 250 ints or 125 longs).
When you want to copy one array of values into another, you need to know how many bytes are used to allocate enough space.
While you may dereference a pointer and get the value, you could dereference the pointer at a different portion of that value, but only if you know how many bytes it is composed of. You could get the high-value of an int by grabbing just the first two bytes, and the low value by getting the last two bytes.
Different architectures may have different sizes for different variables, which would impact all the above points.
Edit:
Also, there are certainly reasons where you may need to know the number of bits that a given variables is made of. If you want 32 booleans, what not a better variable to use than a single int, which is made of 32 bits? Then you can use some constants to create pointers to each bit and now you have an "array" of booleans. These are usually called bit-fields (correct me if I am wrong). In programming, every detail can matter, just not all the time for every application. Just figured that might be an interesting thought exercise.
The answer is simple: the internal representation of most types needs more than one byte. In order to dereference a pointer you (either you or the compiler) need to know how much bytes should be read.
Also consider it when working with strings, you cannot always relay on the terminating \0, hence you need to know how many bytes you have to read. Examples of these are functions like memcpy or strncmp.
Supposed you have an array of variables. Where do you find the variable at non-zero index without knowing its size? And how many bytes do you allocate for non-zero length array of variables?

How are arrays and hash maps constant time in their access?

Specifically: given a hash (or an array index), how does the machine get to the data in constant time?
It seems to me that even passing by all the other memory locations (or whatever) would take an amount of time equal to the number of locations passed (so linear time). A coworker has tried valiantly to explain this to me but had to give up when we got down to circuits.
Example:
my_array = new array(:size => 20)
my_array[20] = "foo"
my_array[20] # "foo"
Access of "foo" in position 20 is constant because we know which bucket "foo" is in. How did we magically get to that bucket without passing all the others on the way? To get to house #20 on a block you would still have to pass by the other 19...
How did we magically get to that
bucket without passing all the others
on the way?
"We" don't "go" to the bucket at all. The way RAM physically works is more like broadcasting the bucket's number on a channel on which all buckets listen, and the one whose number was called will send you its contents.
Calculations happen in the CPU. In theory, the CPU is the same "distance" from all memory locations (in practice it's not, because of caching, which can have a huge impact on performance).
If you want the gritty details, read "What every programmer should know about memory".
Then to understand you have to look at how memory is organized and accessed. You may have to look at the way an address decoder works. The thing is, you do NOT have to pass by all the other addresses to get to the one you want in the memory. You can actually jump to the one you want. Otherwise our computers would be really really slow.
Unlike a turing machine, which would have to access memory sequentially, computers use random access memory, or RAM, which means if they know where the array starts and they know they want to access the 20th element of the array, they know what part of memory to look at.
It is less like driving down a street and more like picking the correct mail slot for your apartment in a shared mailbox.
2 things are important:
my_array has information about where in memory computer must jump to get this array.
index * sizeof type gets offset from beginning of array.
1 + 2 = O(1) where data can be found
Big O doesn't work like that. It's supposed to be a measure of how much computational resources are used by a particular algorithm and function. It's not meant to measure the amount of memory used and if you are talking about traversing that memory, it's still a constant time. If I need to find the second slot of an array it's a matter of adding an offset to a pointer. Now, if I have a tree structure and I want to find a particular node you are now talking about O(log n) because it doesn't find it on the first pass. On average it takes O(log n) to find that node.
Let's discuss this in C/C++ terms; there's some additional stuff to know about C# arrays but it's not really relevant to the point.
Given an array of 16-bit integer values:
short[5] myArray = {1,2,3,4,5};
What's really happened is that the computer has allocated a block of space in memory. This memory block is reserved for that array, is exactly the size needed to hold the full array (in our case 16*5 == 80 bits == 10 bytes), and is contiguous. These facts are givens; if any or none of them are true in your environment at any given time, you're generally at risk for your program crashing due to an access vialoation.
So, given this structure, what the variable myArray really is, behind the scenes, is the memory address of the start of the block of memory. This is also, conveniently, the start of the first element. Each additional element is lined up in memory right after the first, in order. The memory block allocated for myArray might look like this:
00000000000000010000000000000010000000000000001100000000000001000000000000000101
^ ^ ^ ^ ^
myArray([0]) myArray[1] myArray[2] myArray[3] myArray[4]
It is considered a constant-time operation to access a memory address and read a constant number of bytes. As in the above figure, you can get the memory address for each one if you know three things; the start of the memory block, the memory size of each element, and the index of the element you want. So, when you ask for myArray[3] in your code, that request is turned into a memory address by the following equation:
myArray[3] == &myArray+sizeof(short)*3;
Thus, with a constant-time calculation, you have found the memory address of the fourth element (index 3), and with another constant-time operation (or at least considered so; actual access complexity is a hardware detail and fast enough that you shouldn't care) you can read that memory. This is, if you've ever wondered, why indexes of collections in most C-style languages start at zero; the first element of the array starts at the location of the array itself, no offset (sizeof(anything)*0 == 0)
In C#, there are two notable differences. C# arrays have some header information that is of use to the CLR. The header comes first in the memory block, and the size of this header is constant and known, so the addressing equation has just one key difference:
myArray[3] == &myArray+headerSize+sizeof(short)*3;
C# doesn't allow you to directly reference memory in its managed environment, but the runtime itself will use something like this to perform memory access off the heap.
The second thing, which is common to most flavors of C/C++ as well, is that certain types are always dealt with "by reference". Anything you have to use the new keyword to create is a reference type (and there are some objects, like strings, that are also reference types although they look like value types in code). A reference type, when instantiated, is placed in memory, doesn't move, and is usually not copied. Any variable that represents that object is thus, behind the scenes, just the memory address of the object in memory. Arrays are reference types (remember myArray was just a memory address). Arrays of reference types are arrays of these memory addresses, so accessing an object that is an element in an array is a two-step process; first you calculate the memory address of the element in the array, and get that. That is another memory address, which is the location of the actual object (or at least its mutable data; how compound types are structured in memory is a whole other can o' worms). This is still a constant-time operation; just two steps instead of one.

Resources