If each memory address can hold 1 byte (8 bits) of data
That’s not exactly right—it’s not that memory is partitioned into an array of boxes, each of which is 1 byte in size; it’s that 1 byte is the smallest addressable unit of memory. You can address memory in larger increments.
So if you have a pointer char *p
containing some address such as 0x12345670
, that’s essentially just an offset into memory—it points to the start of a region that may comprise a single byte, or multiple bytes, such as an integer, array, or struct. (In fact it’s slightly more complicated, since what you see as a flat address space is actually virtual memory that’s mapped onto physical memory by the operating system kernel, but for the purposes of this explanation it doesn’t make a difference.)
A 32-bit integer with value 0xAABBCCDD
at address p
simply occupies 4 bytes. These bytes may be arranged by the CPU in big-endian order, where the most significant bits are stored at the lowest address:
0x1234566F …
0x12345670 0xAA
0x12345671 0xBB
0x12345672 0xCC
0x12345673 0xDD
0x12345674 …
Or little-endian, where the least significant bits are stored at the lowest address:
0x1234566F …
0x12345670 0xDD
0x12345671 0xCC
0x12345672 0xBB
0x12345673 0xAA
0x12345674 …
A programming language like C abstracts over this somewhat to provide a convenient way to address objects of different sizes. Suppose there is some array of 32-bit integers a
, and p
is the address of the first element: p = &a[0]
. In assembly, if you want to iterate over this array, you need to increment p
by 4 each time to move it to the next integer:
&a[0] == p
&a[1] == p + 4
&a[2] == p + 8
&a[3] == p + 12
&a[4] == p + 16
…
In C, an expression like p + 1
doesn’t just add a number of bytes to the value of p
, it adds multiples of the object size, sizeof(*p)
—so if p
were typed as uint32_t *p
, then a[1]
would be at p + 1
, a[2]
at p + 2
, and so on. Under the hood, p + n
becomes something like (char *)p + n * sizeof(*p)
.
how does the CPU know when to stop reading bits of the memory addresses?
A primitive type like an integer is always a fixed size. When you write *pi += 42
to add 42
to the contents of the 32-bit integer referred to by pi
, that’s translated specifically to a 32-bit indirect-add instruction. A compound type like an array is just a series of values at addresses that are multiples of the object size—your program is responsible for only accessing within the bounds of the array. Higher-level languages insert automatic checks at runtime or compile-time to ensure memory safety by preventing invalid array accesses, among other things.
A dynamically allocated value like the result of malloc
is just a region of memory that the allocator has given you control over, which in turn it obtains from the operating system. You can cast it to whatever type you want, such as an array of custom structures, as long as you only access within the region that you’ve been granted by the allocator.
2000
fit's just fine with anint
, every value in that array may contain at least data ofsizeof(int)
bytes data. What are you asking about?2000
stored in memory?| |
is a memory addressint
probably covers a minimum memory space of 32 bits on most targets, There's only one memory address for eachint
in that array. I don't get what you mean or what confuses you.