In a 32 bit machine, if you copied an int p, it would copy 4 bytes of information, which would be addressed at 0xbeefbeef, 0xbeefbef0, 0xbeefbef1, 0xbeefbef2 respectively.
Is this the same with 64 bit? Or does it store 2 bytes at a single address?
It depends on the architecture. On most "normal" 64-bit systems (e.g. arm64, x86_64, etc.) memory is "byte addressed," so each memory address refers to one byte (so it's the same as your 32-bit example).
There are systems out there which are not byte addressed, and this can included 64-bit architectures. For example, DSPs are a classic example of systems where char can be 32-bits (or more) and an individual byte (or rather, octet) is not addressable.
On a amd64 architecture (also called x86_64 and x64, which is the most common 64-bit architecture), each addressable unit still refers to one byte of memory (8-bits).
Additionally, an int still usually contains 4 bytes of memory (32-bits), though this can vary from compiler to compiler (as it also does on 32-bit systems).
What will be different is the size of a pointer. On a 32-bit system, pointers are normally 32-bits, but are 64-bits on a 64-bit system (8 bytes). This will allow the computer to access more bytes of memory, but each byte is still 8-bits long.
Related
As far as I know, the pointer data types (char *, etc.) get the word size of the system. If I think of memory as a grid, one field is the size of one word (so 4 bytes for a 32-bit system and 8 bytes for a 64-bit system). So the idea is to give the pointer exactly one field because that's convenient (better performance?). But then I wonder why a simple char gets only one byte. That would be 1/4 of a field. Why is that? And what happens to the remaining 3 bytes of the box?
The correct way to make the conversion between pointer and integers in C is via the type intptr_t. This is the optimal way to keep a pointer into an integer.
Your question is in link with the hardware of the computers. The C language influenced the hardware design.
There is a distinction between data path and control. Data path is the hard-coded part of the hardware and it contains buses of N wires. There are buses for addresses and buses for data and they do not have the same number of wires all the time. The C language sets the size of a pointer to object big enough to cover all the possible addresses on the target address buses (in some architectures the code is accessed on different buses, and there the size of pointer to function may differ). For practical reasons, the control contains instructions to access the data using different sizes, depending on the need. If you need to work with small integers there is no reason to access them from 4 to 4 bytes. They can be aligned more compactly.
But yes, there are C compilers that compile a char in 4 bytes (I have never seen any, but they exist).
the pointer data types (char *, etc.) get the word size of the system
Not exactly: they usually have the size of the address space, ie: enough bits to address any data in RAM. Note however that it can be more bits than the word size (the size of a typical CPU register) as was the case in some older systems: 8088, 8086, 80186 and 80286 had 16-bit registers but an address space ranging from 20 to 24 bits, requiring a pair of words to express an address. These systems actually had various compilation modes where pointers could be 16-bit or 32-bit depending on the amount of memory the program could use.
`But then I wonder why a simple char gets only one byte?
A byte is, by definition, the smallest item of memory that can be addressed directly. The C language maps this to the char type. On most systems, this is an octet comprising 8 bits, which happens to be the smallest possible size for a char. The address space is expressed in this unit, even if the data bus is wider. For example 64-bit intel processors typically have a 128-bit data bus, but addresses are still expressed in units of 8-bits. Some specific CPUs such as DSPs (digital signal processors) may not have this capability and can only address 16-bit or even 32-bit words. On these systems, a byte can be 16-bit or even 32-bit wide and the C compiler either uses this width for the char type or emulates a smaller char type in software.
what happens to the remaining 3 bytes of the box?
Nothing special, the box is a pack of 2, 4, 8 or more bytes, each of which can be addressed directly and independently, either as the hardware allows it or through software emulation.
I just restart the C programming study. Now, I'm studying the memory storage capacity and the difference between bit and byte. I came across to this definition.
There is a calculation to a 32 bits system. I'm very confused, because in this calculation 2^32 = 4294967296 bytes and it means about 4 Gigabyte. My question is: Why 2 raised to 32 power results in a number in bytes instead of bits ?
Thanks for helping me.
Because the memory is byte-addressable (that is, each byte has its own address).
There are two ways to look at this:
A 32-bit integer can hold one of 2^32 different values. Thus, a uint32_t can represent the values from 0 to 4294967295.
A 32-bit address can represent 2^32 different addresses. And as Scott said, on a byte-addressable system, that means 2^32 different bytes can be addressed. Thus, a process with 32-bit pointers can address up to 4 GiB of virtual memory. Or, a microprocessor with a 32-bit address bus can address up to 4 GiB of RAM.
That description is really superficial and misses a lot of important considerations, especially as to how memory is defined and accessed.
Fundamentally an N-bit value has 2N possible states, so a 16-bit value has 65,536 possible states. Additionally, memory is accessed as bytes, or 8-bit values. This was not always the case, older machines had different "word" sizes, anywhere from 4 to 36 bits per word, occasionally more, but over time the 8-bit word, or "byte", became the dominant form.
In every case a memory "address" contains one "word" or, on more modern machines, "byte". Memory is measured in these units, like "kilowords" or "gigabytes", for reasons of simplicity even though the individual memory chips themselves are specified in terms of bits. For example, a 1 gigabyte memory module often has 8 gigabit chips on it. These chips are read at the same time, the resulting data combined to produce a single byte of memory.
By that article's wobbly definition this means a 16-bit CPU can only address 64KB of memory, which is wrong. DOS systems from the 1980s used two pointers to represent memory, a segment and an offset, and could address 16MB using an effective 24-bit pointer. This isn't the only way in which the raw pointer size and total addressable memory can differ.
Some 32-bit systems also had an alternate 36-bit memory model that allowed addressing up to 64GB of memory, though an individual process was limited to a 4GB slice of the available memory.
In other words, for systems with a singular pointer to a memory address and where the smallest memory unit is a byte then the maximum addressable memory is 2N bytes.
Thankfully, since 64-bit systems are now commonplace and a computer with > 64GB of memory is not even exotic or unusual, addressing systems are a lot simpler now then when having to work around pointer-size limitations.
We say that memory is byte-addressable, you can think like byte is the smallest unit of memory so you are not reading by bits but bytes. The reason might be that the smallest data type is 1 byte, even boolean type in c/c++ is 1 byte.
Running the following on Linux x86-64 compiled with gcc -m32
#include <stdio.h>
#include <limits.h>
int main() {
int a = 4;
int* ptr = &a;
printf("int* is %d bits in size\n", CHAR_BIT * sizeof(ptr));
return 0;
}
results in
int* is 32 bits in size
Why I convinced myself it ought to be 64 bits (prior to executing): since it is running on a 64-bit computer in order to address the memory we need 64 bits. Since &a is the address of where value 4 is stored it should be 64 bits. The compiler could implement a trick by having the same offset for all pointers since it is running in the compatibility mode, but it couldn't guarantee congruent data after calling malloc multiple times. This is wrong. Why?
On the hardware level, your typical x86-64 processor has a 32-bits compatibility mode, where it behaves like a x86 processor. That means memory is addressed using 4 bytes, hence your pointer is 32 bits.
On the software level, the 64 bits kernel allows 32 bits processes to be run in this compatibility mode.
This is how 'old' 32 bits programs can run on 64 bits machines.
The compiler, particularly with the -m32 flag, writes code for x86 addressing, so that's why int* is also 32 bits.
Modern CPUs have a memory management unit, it makes possible that every program has its own address space. You could even have two different programs using the same addresses. This unit is also what detects segmentation faults (access violations). With this, the addresses a program uses are not the same as the addresses on the address bus that connects the CPU and the peripherials including RAM, so it's no problem for the OS to assign 32-bit addresses to a program.
An x86-64 machine running a 64bit OS runs 32bit processes in "compat" mode, which is different from "legacy" mode. In compat mode, user-space (i.e. the 32bit program's point of view) works the same as on a system in legacy mode (32bit everything).
However, the kernel is still 64bits, and can map the compat-mode process's virtual address space anywhere in physical address space. (so two different 32b processes can each be using 4GB of RAM.) IDK if the page tables for a compat process need to be different from 64bit processes. I found http://wiki.osdev.org/Setting_Up_Long_Mode, which has some stuff but doesn't answer that question.
In compat mode, system calls switch the CPU to 64b long mode, and returns from system calls switch back. Kernel functions that take a user-space pointer as an argument need simple wrappers to do whatever is necessary to get the appropriate address for use from kernel-space.
The high level answer is that there's hardware support for everything compat mode needs to be just as fast as legacy mode (32bit kernel).
IIRC, 32bit virtual addresses get zero-extended to 64bit by the MMU hardware, so the kernel just sets up the page tables accordingly.
If you use an address-size override prefix in 64bit code, the 32-bit address formed from the 32bit registers involved will be zero-extended. (There's an x32 ABI for code that doesn't need more than 4GB of RAM, and would benefit from smaller pointers, but still wants the performance benefit of more registers, and having them be 64b.)
Why there is no concept of near,far & huge pointer in a 32 bit compiler? As far as I understand, programs created on 16 bit 8086 architecture complier can have 1 mb size in which the data segment, graphics segments etc are there. To access all those segment and to maintain pointer increment concept we need these various pointers, but why in 32 bit its not necessary?
32-bit compilers can address the entire address space made available to the program (or to the OS) with a single, 32-bit pointer. There is no need for basing because the pointer is large enough to address any byte in the available address space.
One could theoretically conceive of a 32-bit OS that addresses > 4GB of memory (and therefore would need a segment system common with 16-bit OS's), but the practicality is that 64-bit systems became available before the need for that complexity arose.
why there is no concept of near,far & huge pointer in 32 bit compiler?
It depends on the platform and the compiler. Open Watcom C/C++ supports near, far and huge pointers in 16-bit code and near and far pointers in 32-bit code.
As i know programs created on 16 bit 8086 architecture complier can have 1 mb size in which datasegment graphics segments etc are there. to access all those segment and to maintain pointer increment concept we need these various pointers, but why in 32 bit its not necessary?
Because in most cases near 32-bit pointers are enough to cover the entire address space (all 232 bytes = 4 GB of it), which is not the case with near or far 16-bit pointers that as you said yourself can only cover up to 1 MB of memory (strictly speaking, in 16-bit protected mode of 80286+, you can use 16-bit far pointers to address up to at least 16 MB of memory, that's because those pointers are relative to the beginning of segments and segments on 80286+ can start anywhere in the first 16 MB since the segment descriptors in the global descriptor table (GDT) or the local descriptor table (LDT) reserve 24 bits for the start address of a segment (224 bytes = 16 MB)).
Can we anyhow change the size of the pointer from 2 bytes so it can occupy more than 2 bytes?
Sure, compile for a 32 (or 64) bit platform :-)
The size of pointers is platform specific, it would be 2 bytes only on 16-bit platforms (which have not been widely used for more than a decade - nowadays all mainstream [update](desktop / laptop / server)[/update] platforms are at least 32 bits).
If your pointer size is 2 byte that means you're running on a 16-bit system.
The only way to increase the pointer size is to use a 32-bit or 64-bit system instead (which would mean any desktop or laptop computer built in the last 15 years or so).
If you're running on some embedded device that uses 16-bit, your only option would be to switch to another device which uses 32-bits (or just live with your pointers being 16-bit).
When a processor is said to be "X-bit" (where X is 16, 32, 64, etc), that X refers to the size of the memory address register. Thus a 16-bit system has a memory address register of 2 bytes.
You cannot cast a 4-byte address to anything smaller because it would lose part of where it's pointing to. (A 2-byte memory address register can only point to 2^16=64KB of memory, whereas a 4-byte register can point to 2^32=4GB of memory.)
You can always "step-up" (ie, run a 32-bit software application on a 64-bit computer) because there's no loss in pointer range. But you can never step down, which is why 64-bit programs don't run on 32-bit systems.
Think of a pointer as a number, only instead of an actual value used for computation, it's the number of a 'slot' in the memory map of the system.
A pointer must be able to represent the highest position of the memory map. That is, it must have at least the amount of bytes required to represent the number of the highest position.
In a 16-bit system, the highest possible position is 0xFFFF (a 16-bit number with all the bits set to 1). A pointer must also have 16 bits, so it can reach that number.
Generalizing, in an X-bit system, a pointer will have X bits.
You can store a pointer in a larger variable, the same way you can store the number 1 in a char, in an int, or an unsigned long long if you wanted to; but there's little point to that: think that, the same way a shorter pointer won't be able to reach the highest memory position, a longer pointer would be able to point to things that can't actually exist in memory, so why have it?
Also, you'd have to 'trick' the compiler for that. If you use the pointer notation in your code, the compiler will always use the correct amount of bytes for it. You can instruct the compiler to compile for another platform, though.