%
I have a doubt about how the function obtains the data, for each specifier printf()
. I assume that, for each specifier, it gets the values from the stack where the variables passed by arguments were pushed. Since printf()
it has a variable list of arguments, inside the function there is no way to know how many arguments and what type have been passed. Therefore, it is the information detailed by the specifiers that is used to correctly obtain the values of the passed arguments.
My doubt comes from the following: I suppose that for each specifier indicated, a value is obtained from the stack of the size specified by the specifier ( %d
para int
, %ld
para long
, %lld
para long long
, etc). So, if printf()
there are two specifiers inside %d
and %lld
, it will first get a value int
from the stack and then get a value long long
from the stack that contains the values of the passed arguments.
Explained the above, if we look at the following code:
#include <stdio.h>
#include <limits.h>
int main(void)
{
unsigned int ui = 0;
unsigned long long ulli = ULLONG_MAX;
printf("TAMAÑOS\nint:%8zu\nlong long: %zu\n",
sizeof(int), sizeof(long long));
printf("\nulli: %u\nui: %llu\nulli: %u", ulli, ui, ulli);
return 0;
}
With the following output:
SIZES int: 4 long long: 8 ulli: 4294967295 ui: 0 ulli: 4294967295
We can see that, by printing the second value corresponding to the variable ui
using the specifier %llu
, the value displayed is 0
but, even though the value of the variable ui
is 0
and it seems to be doing it correctly, why is it displaying its value?
Shouldn't it have printed a second value unsigned long long
from the stack, after getting a first value unsigned int
for the previous specifier %u
, thus getting bits from the first or third passed variables? That is, the first argument passed is an long long
8-byte with all bits 1. If that value is given 4 bytes to print a value of type unsigned int
due to the first specifier %u
, wouldn't it start printing the value of the second specifier %llu
from of the 4th byte of the first argument unsigned long long
passed (with all bits set to 1) and thus giving a value other than 0
?
If the second specifier is to get a value of type unsigned long long
8 bytes but the second variable is of type unsigned int
4 bytes, wouldn't the displayed value get some bits from the other two variables?
PS: The specifiers intentionally do not match the data types of the variables passed by arguments in order to understand the problem.
You believe wrong; the explanation lies in the function call convention .
Roughly speaking, the calling convention indicates in which order the arguments are introduced/extracted on the stack, and at what point the stack is cleaned (before returning from the function, or after doing so), the alignment of the data on the stack, ...
This is part of the ABI (Application Binary Interface) of each platform; It is different for each Operating System and for each architecture.
Recall that C does not have the concept of a stack ; that is of a lower level than the language itself; in it, it only talks about arguments of a function and its return .
You can take a look at this table , where some calling conventions are indicated. For x86_64, 6 registers are used; if there are more arguments, the stack is invoked. And this is aligned in blocks of 16 bytes ; that is, even if you want to introduce a value that is only 4 bytes long, in reality the compiler will use 16.
There you have the reason: you are putting data in and out that is 16 bytes in size; width more than enough so that the bits do not step on each other :-)
In case someone is interested and wants to go deeper, I quote some:
stdcall
,pascal
,fastcall
,cdecl
, ...