Redescobrindo Assembly

Cada tipo numérico ocupa uma quantidade diferente de bytes na memória, #SQN

Cada tipo numérico precisa de uma quantidade específica de bytes em memória para ser representado. Quanto maior a quantidade de bytes demandada, maior o range de valores suportados.

#include 

int main() {
	std::cout << "sizeof(char)      : " << sizeof(char) << std::endl;
	std::cout << "sizeof(short)     : " << sizeof(short) << std::endl;
	std::cout << "sizeof(int)       : " << sizeof(int) << std::endl;
	std::cout << "sizeof(long)      : " << sizeof(long) << std::endl;
	std::cout << "sizeof(long long) : " << sizeof(long long) << std::endl;

	std::cout << "Range for int : "
		<< std::numeric_limits<int>::min()
		<< " - "
		<< std::numeric_limits<int>::max() << std::endl;

	std::cout << "Range for long: "
		<< std::numeric_limits<long>::min()
		<< " - "
		<< std::numeric_limits<long>::max() << std::endl;
}

É interessante notar, entretanto, que, por motivos históricos, a Microsoft escolheu manter, em C++, o tipo long com 4 bytes, mesmo em sistemas de 64 bits. Isso significa que, modernamente, em C++, int e long demandam a mesma quantidade de bytes e suportam o mesmo range de valores.

Sabendo que cada tipo demanda uma quantidade de bytes diferente, seria natural assumir que esses tipos ocupassem quantidades diferentes de memória na stack.

#include <iostream>

void doubleIt(
	char a,
	short b,
	int c,
	long long d,
	char* da,
	short* db,
	int* dc,
	long long* dd
)
{
	*da = a + a;
	*db = b + b;
	*dc = c + c;
	*dd = d + d;
}

int main() {
	const char a = 1;
	const short b = 2;
	const int c = 4;
	const long long d = 8;
	
	char da = 0;
	short db = 0;
	int dc = 0;
	long long dd = 0;

	doubleIt(a, b, c, d, &da, &db, &dc, &dd);

	std::cout <<
		"double of " << +a << " is " << +da << std::endl <<
		"double of " << b << " is " << db << std::endl <<
		"double of " << c << " is " << dc << std::endl <<
		"double of " << d << " is " << dd << std::endl;

}

Entretanto, este não é o caso.

	.model flat,c
        .code

doubleIt_ proc

	push ebp
	mov ebp, esp

	; da
	mov al, [ebp + 8]
	add al, al
	mov ebx,[ebp + 28]
	mov [ebx], al

	; db
	mov ax, [ebp + 12]
	add ax, ax
	mov ebx,[ebp + 32]
	mov [ebx], ax

	; dc
	mov eax, [ebp + 16]
	add eax, eax
	mov ebx,[ebp + 36]
	mov [ebx], eax

	; dd
	mov eax, [ebp + 20]
	mov edx, [ebp + 24]
	add eax, eax
	adc edx, edx
	mov ebx,[ebp + 40]
	mov [ebx], eax
	mov [ebx+4], edx

	pop ebp
	ret

doubleIt_ endp

	end 

Em arquiteturas de 32 bits, o transporte de dados com tamanhos múltiplos desse tamanho é mais eficiente. Por essa razão, os valores na stack acabam organizados em múltiplos de 32 bits.

Dessa forma, tipos que ocupam menos que 32 bits são complementados com “espaço inútil”.

Não adianta escolher tipos menores para “economizar espaço” na stack.

Em Resumo
  • O fato

    Tipos numéricos são organizados na Stack em espaços múltiplos do tamanho ótimo para transporte pela arquitetura do sistema (32 bits).

Elemar Júnior

Microsoft Regional Director e Microsoft MVP. Atua, há mais de duas décadas, desenvolvendo software e negócios digitais de classe mundial. Teve o privilégio de ajudar a mudar a forma como o Brasil vende, projeta e produz móveis através de software. Hoje, seus interesses técnicos são arquiteturas escaláveis. bancos de dados e ferramentas de integração. Além disso, é fascinado por estratégia e organizações exponenciais.

Talvez você goste também

Carregando posts…

Mais posts da série Redescobrindo Assembly

2 Comentários
  1. Thiago Lunardi

    Ok, Não há memory leak, mas e performance? Realizar processamentos com tipos menores é mais barato? Acessar, ler e transportar valores de tipos menores trazem aumento de velocidade no processamento contrar tipos maiores?

    1. Elemar Júnior

      Para tipos primitivos, na stack, a princípio não há ganho nenhum percebido para tipos “menores”. O transporte geralmente acontece em 32 bits mesmo.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *