without knowing it, @ first, wrote code involved long long
variable. mistakenly treating long
, passed printf
, printed value contained (as high bits zeroes).
eventually, found out using long long
, , got me interested in why printf
seems resilient against kind of error had made. wrote this:
int main() { long = 1; long long b = 2; long c = 3; long d = 4; long long e = 5; long f = 6; long g = 7; long long h = 8; long = 9; long long j = 10; printf("%d %i64d %d %d %i64d %d %d %i64d %d %i64d\n", a, b, c, d, e, f, g, h, i, j); printf("%i64d %i64d %i64d %i64d %i64d %i64d %i64d %i64d %i64d %i64d\n", a, b, c, d, e, f, g, h, i, j); printf("%d %d %d %d %d %d %d %d %d %d\n", a, b, c, d, e, f, g, h, i, j); getchar(); return 0; }
the first of printf
s associates right format specifier type of each subsequent argument after format string. second applies specifier long long
, , third specifies plain old long
(well, int
, precise, right?).
here's output:
1 2 3 4 5 6 7 8 9 10 1 2 3 -3689348818177884156 5 -3689348818177884154 -3689348818177884153 8 -3689348818177884151 10 1 2 3 4 5 6 7 8 9 10
now kind of puzzled me, because expected compiled code call printf
pushed 4 bytes onto stack arguments long
s, , 8 bytes long long
s. however, if case, printf
code have been looking in wrong places after processing first incorrect format specifier. yet, output shows, printf
never got lost, associating matching specifier argument in each case. second line fails print right values, that's reason turns out make perfect sense, leads question.
to find out going on, had compiler (vs2015's vc++, x64) produce assembly listing. here's first call printf
:
; 20 : printf("%d %i64d %d %d %i64d %d %d %i64d %d %i64d\n", a, b, c, d, e, f, g, h, i, j); mov rax, qword ptr j$[rbp] mov qword ptr [rsp+80], rax mov eax, dword ptr i$[rbp] mov dword ptr [rsp+72], eax mov rax, qword ptr h$[rbp] mov qword ptr [rsp+64], rax mov eax, dword ptr g$[rbp] mov dword ptr [rsp+56], eax mov eax, dword ptr f$[rbp] mov dword ptr [rsp+48], eax mov rax, qword ptr e$[rbp] mov qword ptr [rsp+40], rax mov eax, dword ptr d$[rbp] mov dword ptr [rsp+32], eax mov r9d, dword ptr c$[rbp] mov r8, qword ptr b$[rbp] mov edx, dword ptr a$[rbp] lea rcx, offset flat:??_c@_0cl@ghcekcad@?$cfd?5?$cfi64d?5?$cfd?5?$cfd?5?$cfi64d?5?$cfd?5?$cfd?5?$cfi64d@ call printf
now, has been long time since did assembly programming, but, if read correctly, appears each argument right left being put memory locations relative rsp
register are, every 1 of them, 8 bytes apart each other, regardless of whether or not value requires 8 bytes representation. (i note statement not apply values a
, b
, , c
, kept in registers, not memory; optimization short argument lists, perhaps?)
thus, makes sense: printf
never gets lost, because knows (or coder wrote knew) each argument found 8 bytes away neighbor, regardless of argument's size. (the few values printed incorrectly can, think, explained fact compiled code stored four-byte dword
s values, , there may have been non-zero bytes in high 4 bytes of qword
printf
expected find in locations.)
so, seems me compiler maintains stack (is "stack" right word here?) eight-byte entries in it, regardless of whether or not 8 needed.
why?
update:
the question i've asked (and hans passant has answered) doesn't rely on printf
or variadic argument lists. it's inherent in how fifth , higher arguments functions handled x64 architecture.
for example, calling function:
void sub(long a, long b, long c, long d, long e, long f) { }
gets assembler code:
; 28 : sub(a, c, d, f, g, i); mov eax, dword ptr i$[rbp] mov dword ptr [rsp+40], eax mov eax, dword ptr g$[rbp] mov dword ptr [rsp+32], eax mov r9d, dword ptr f$[rbp] mov r8d, dword ptr d$[rbp] mov edx, dword ptr c$[rbp] mov ecx, dword ptr a$[rbp] call ?sub@@yaxjjjjjj@z ; sub
where, again, see arguments long
s passed function in memory locations 8 bytes apart.
Comments
Post a Comment