Computer Architecture: Integer Types in Memory

Integer Types in Memory

In this Computer Architecture video, we demonstrate how integer variable types are represented and stored in memory. We revisit how unsigned integers are stored as bits and how that representation translates to a signed modular equivalent using a two's complement representation.

Two's Complement

In a signed two's complement representation, the larger half of the range for the unsigned values is wrapped around to the negative, modular congruent values. For example, we can represent the range of integer values 0 to 255 with one byte or 8 bits. For a two's complement representation of a byte, we can represent the integers from -128 to 127. In this case, the values from 128 to 255 become -128 to -1, respectively. Note that the negative values are equivalent to their corresponding positive values modulo 256.

For our first program, we output the unsigned and signed values for the bit patterns first where all of the bits are on and then when only the most significant bit is on. These patterns have the unsigned values 255 and 128, respectively. They are equal to -1 and -128 in a signed two's complement representation, as shown below. The cast to the (int)-type is required to output the integer value, rather than the character representation.

#include <iostream>

int main()
{
	using namespace std;

	unsigned char ucAllBits = 0xFF; // equal to 255
	cout << "Unsigned char value = " << (int)ucAllBits << endl;
	unsigned char ucMSBit = 0x80; // equal to 128
	cout << "Unsigned char value = " << (int)ucMSBit << endl;

	char cAllBits = 0xFF;  // equal to 255
	cout << "Signed char value = " << (int)cAllBits << endl;
	char cMSBit = 0x80;  // equal to 128
	cout << "Signed char value = " << (int)cMSBit << endl;

	return 0;
}

Executing this program, by pressing (CTRL + F5), we get this:

Output1

In our second program below, are display the sizes of various integer types. Although the char and bool types are not used specifically for integer values, they are represented bitwise by integer values, and we can perform all of the integer operations on them--like addition, multiplication, etc. All of the types below, except the bool, have an associated signed type that is the same size.

#include <iostream>

int main()
{
	using namespace std;

	cout << "A char is " << sizeof(char) << " bytes." << endl;
	cout << "A bool is " << sizeof(bool) << " bytes." << endl;
	cout << "A short is " << sizeof(short) << " bytes." << endl;
	cout << "An int is " << sizeof(int) << " bytes." << endl;
	cout << "A long long is " << sizeof(long long) << " bytes." << endl;

	return 0;
}

Executing this program, by pressing (CTRL + F5), outputs the sizes of the types in bytes. Note that 2 bytes are called a word, 4 bytes are called a double word, and 8 bytes are called quadruple word in Microsoft Windows' terminology.

Output2

For our last program, we demonstrate how the bytes of a multi-byte integer type are arranged in memory. To start, we output the size of an unsigned int, its address in memory, and its decimal value. The unsigned integer variable is initialized with the byte values 6, 7, 9, and 4--going from the most significant to the least significant. Each of these bytes is printed in the for-loop below in the order in which they are stored in memory. In Windows, the least significant byte comes first, which is called little-endian ordering.

#include <iostream>

int main()
{
	using namespace std;

	unsigned int uiDWord = 0x06070904;

	cout << "The size is " << sizeof(uiDWord) << " bytes." << endl;
	cout << "Address: " << &uiDWord << endl;
	cout << "Decimal Value: " << uiDWord << endl;

	// Output the value and address of each byte
	char* cpByte = (char*)&uiDWord;
	for (unsigned int uiI = 0; uiI < 4; ++uiI) {
		cout << "byte " << uiI << " = " << (int)cpByte[uiI];
		cout << "     address = " << (int*)&(cpByte[uiI]) << endl;
	}

	return 0;
}

Again, executing this program, by pressing (CTRL + F5), we see the output:

Output3