I've tried compiling this code both with Clang and GCC:
struct s { int _[50]; };
void (*pF)(const struct s), (*pF1)(struct s), (*pF2)(struct s *);
main()
{
struct s a;
pF2(&a);
pF(a), pF1(a);
}
The result is the same. Although the call to pF isn't allowed to modify its only argument, the object a is copied for the second call to pF1. Why is this?
Here is the assembly output (from GCC):
; main
push rbx
sub rsp, 0D0h
mov rbx, rsp
mov rdi, rsp
call cs:pF2
;create argument for pF1 call (as there the argument is modified)
;and copy the local a into it
;although it seems not needed because the local isn't futher read anyway
sub rsp, 0D0h
mov rsi, rbx
mov ecx, 19h
mov rdi, rsp
;
rep movsq
call cs:pF
;copy the local a into the argument created once again
;though the argument cannot be modified by the function pointed by pF
mov rdi, rsp
mov rsi, rbx
mov ecx, 19h
rep movsq
;
call cs:pF1
add rsp, 1A0h
xor eax, eax
pop rbx
retn
Can't the optimizer see that as the function pointed by pF can't modify its parameter (as it's declared const) and so omit the last copying operation? Also, recently I saw that, as the variable a isn't further read in the code, it could use its storage for the function arguments.
The same code can be written as:
; main
push rbx
sub rsp, 0D0h
mov rdi, rsp
call cs:pF2
call cs:pF
call cs:pF1
add rsp, 0D0h
xor eax, eax
pop rbx
retn
I'm compiling with -O3 flag. Am I missing something?
It's the same even if I don't invoke UB (as the functions pointers are by default NULL) and I instead initialize them like:
#include <stdio.h>
struct s { int _[50]; };
extern void f2(struct s *a);
void (*pF)(const struct s), (*pF1)(struct s), (*pF2)(struct s *) = f2;
extern void f1(struct s a)
{
a._[2] = 90;
}
extern void f(const struct s a)
{
for(size_t i = 0; i < sizeof(a._)/sizeof(a._[0]); ++i)
printf("%d\n", a._[i]);
}
extern void f2(struct s *a)
{
a->_[6] = 90;
pF1 = f1, pF = f;
}