tl;dr: How can I properly generate a variable length bitfield in C?
I have a structure containing a variable number of elements for instance:
struct s{
int v1;
int v2;
/* ... */
int vn;
}
I'd like to create an anonymous bitfield of the size of the struct. Since i want low coupling between my two structs I do like this:
struct x{
unsigned long a:sz(struct s);
};
This code works but leads to issues:
What can I do if the struct has a size above 64 bits?
I can use several ints to store my bitfield but that gets messy and overkill.
I could also use a pointer to a void* but I suspect performance loss
By design my bitfield will generate AND for each operation over it deteriorate performances as seen in the following code
int main() {
560: 41 54 push %r12
}
__fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
562: 4c 8d 25 cb 01 00 00 lea 0x1cb(%rip),%r12 # 734 <_IO_stdin_used+0x4>
569: 55 push %rbp
56a: bd 64 00 00 00 mov $0x64,%ebp
56f: 53 push %rbx
570: bb 04 00 00 00 mov $0x4,%ebx
575: 0f 1f 00 nopl (%rax)
struct x x = { .a = 3, };
for(int i=0;i <100; ++i){
++x.a;
printf("%u\n", x.a);
578: 0f b6 d3 movzbl %bl,%edx
57b: 31 c0 xor %eax,%eax
57d: 83 c3 01 add $0x1,%ebx
580: 4c 89 e6 mov %r12,%rsi
583: bf 01 00 00 00 mov $0x1,%edi
588: 83 e3 0f and $0xf,%ebx
58b: e8 b0 ff ff ff callq 540 <__printf_chk#plt>
for(int i=0;i <100; ++i){
590: 83 ed 01 sub $0x1,%ebp
593: 75 e3 jne 578 <main+0x18>
}
return 0;
}
595: 5b pop %rbx
596: 31 c0 xor %eax,%eax
598: 5d pop %rbp
599: 41 5c pop %r12
59b: c3 retq
59c: 0f 1f 40 00 nopl 0x0(%rax)
Ideally I'd like to do something similar to the following pseudo-code (but this is obviously invalid in C).
#define varint(sz) (sz > 32) ? \
((sz>64) ? "#error size too huge" : int64_t) : \
((sz>16) ? int32_t: int16_t)
varint(sizeof(s)/sizeof(int)) mybitfield = 0;
How can I properly generate a variable length bitfield in C?
Related
I've created the following function in c as a demonstration/small riddle about how the stack works in c:
#include "stdio.h"
int* func(int i)
{
int j = 3;
j += i;
return &j;
}
int main()
{
int *tmp = func(4);
printf("%d\n", *tmp);
func(5);
printf("%d\n", *tmp);
}
It's obviously undefined behavior and the compiler also produces a warning about that. However unfortunately the compilation didn't quite work out. For some reason gcc replaces the returned pointer by NULL (see line 6d6).
00000000000006aa <func>:
6aa: 55 push %rbp
6ab: 48 89 e5 mov %rsp,%rbp
6ae: 48 83 ec 20 sub $0x20,%rsp
6b2: 89 7d ec mov %edi,-0x14(%rbp)
6b5: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
6bc: 00 00
6be: 48 89 45 f8 mov %rax,-0x8(%rbp)
6c2: 31 c0 xor %eax,%eax
6c4: c7 45 f4 03 00 00 00 movl $0x3,-0xc(%rbp)
6cb: 8b 55 f4 mov -0xc(%rbp),%edx
6ce: 8b 45 ec mov -0x14(%rbp),%eax
6d1: 01 d0 add %edx,%eax
6d3: 89 45 f4 mov %eax,-0xc(%rbp)
6d6: b8 00 00 00 00 mov $0x0,%eax
6db: 48 8b 4d f8 mov -0x8(%rbp),%rcx
6df: 64 48 33 0c 25 28 00 xor %fs:0x28,%rcx
6e6: 00 00
6e8: 74 05 je 6ef <func+0x45>
6ea: e8 81 fe ff ff callq 570 <__stack_chk_fail#plt>
6ef: c9 leaveq
6f0: c3 retq
This is the disassembly of the binary compiled with gcc version 7.5.0 and the -O0-flag; no other flags were used. This behavior makes the entire code pointless, since it's supposed to show how the stack behaves across function-calls. Is there any way to achieve a more literal compilation of this code with a at least somewhat up-to-date version of gcc?
And just for the sake of curiosity: what's the point of changing the behavior of the code like this in the first place?
Putting the return value in a pointer variable seems to change the behavior of the compiler and it generates the assembly code that returns a pointer to stack:
int* func(int i) {
int j = 3;
j += i;
int *p = &j;
return p;
}
I have the following code:
void cp(void *a, const void *b, int n) {
for (int i = 0; i < n; ++i) {
((char *) a)[i] = ((const char *) b)[i];
}
}
void _start(void) {
char buf[20];
const char m[] = "123456789012345";
cp(buf, m, 15);
register int rax __asm__ ("rax") = 60; // exit
register int rdi __asm__ ("rdi") = 0; // status
__asm__ volatile (
"syscall" :: "r" (rax), "r" (rdi) : "cc", "rcx", "r11"
);
__builtin_unreachable();
}
If I compile it with gcc -nostdlib -O1 "./a.c" -o "./a", I get a functioning program, but if I compile it with -O2, I get a program that generates a segmentation fault.
This is the generated code with -O1:
0000000000001000 <cp>:
1000: b8 00 00 00 00 mov $0x0,%eax
1005: 0f b6 14 06 movzbl (%rsi,%rax,1),%edx
1009: 88 14 07 mov %dl,(%rdi,%rax,1)
100c: 48 83 c0 01 add $0x1,%rax
1010: 48 83 f8 0f cmp $0xf,%rax
1014: 75 ef jne 1005 <cp+0x5>
1016: c3 retq
0000000000001017 <_start>:
1017: 48 83 ec 30 sub $0x30,%rsp
101b: 48 b8 31 32 33 34 35 movabs $0x3837363534333231,%rax
1022: 36 37 38
1025: 48 ba 39 30 31 32 33 movabs $0x35343332313039,%rdx
102c: 34 35 00
102f: 48 89 04 24 mov %rax,(%rsp)
1033: 48 89 54 24 08 mov %rdx,0x8(%rsp)
1038: 48 89 e6 mov %rsp,%rsi
103b: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
1040: ba 0f 00 00 00 mov $0xf,%edx
1045: e8 b6 ff ff ff callq 1000 <cp>
104a: b8 3c 00 00 00 mov $0x3c,%eax
104f: bf 00 00 00 00 mov $0x0,%edi
1054: 0f 05 syscall
And this is the generated code with -O2:
0000000000001000 <cp>:
1000: 31 c0 xor %eax,%eax
1002: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
1008: 0f b6 14 06 movzbl (%rsi,%rax,1),%edx
100c: 88 14 07 mov %dl,(%rdi,%rax,1)
100f: 48 83 c0 01 add $0x1,%rax
1013: 48 83 f8 0f cmp $0xf,%rax
1017: 75 ef jne 1008 <cp+0x8>
1019: c3 retq
101a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
0000000000001020 <_start>:
1020: 48 8d 44 24 d8 lea -0x28(%rsp),%rax
1025: 48 8d 54 24 c9 lea -0x37(%rsp),%rdx
102a: b9 31 00 00 00 mov $0x31,%ecx
102f: 66 0f 6f 05 c9 0f 00 movdqa 0xfc9(%rip),%xmm0 # 2000 <_start+0xfe0>
1036: 00
1037: 48 8d 70 0f lea 0xf(%rax),%rsi
103b: 0f 29 44 24 c8 movaps %xmm0,-0x38(%rsp)
1040: eb 0d jmp 104f <_start+0x2f>
1042: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
1048: 0f b6 0a movzbl (%rdx),%ecx
104b: 48 83 c2 01 add $0x1,%rdx
104f: 88 08 mov %cl,(%rax)
1051: 48 83 c0 01 add $0x1,%rax
1055: 48 39 f0 cmp %rsi,%rax
1058: 75 ee jne 1048 <_start+0x28>
105a: b8 3c 00 00 00 mov $0x3c,%eax
105f: 31 ff xor %edi,%edi
1061: 0f 05 syscall
The crash happens at 103b, instruction movaps %xmm0,-0x38(%rsp).
I noticed that if m contains less than 15 characters, then the generated code is different and the crash does not happen.
What am I doing wrong?
_start is not a function. It's not called by anything, and on entry the stack is 16-byte aligned, not (as the ABI requires) 8 bytes away from 16-byte alignment.
(The ABI requires 16-byte alignment before a call, and call pushes an 8-byte return address. So on function entry RSP-8 and RSP+8 are 16-byte aligned.)
At -O2 GCC uses alignment-required 16-byte instructions to implement the copy done by cp(), copying the "123456789012345" from static storage to the stack.
At -O1, GCC just uses two mov r64, imm64 instructions to get bytes into integer regs for 8-byte stores. These don't require alignment.
Workarounds
Just write a main in C like a normal person if you want everything to work.
Or if you're trying to microbenchmark something light-weight in asm, you can use gcc -nostdlib -O3 -mincoming-stack-boundary=3 (docs) to tell GCC that functions can't assume they're called with more than 8-byte alignment. Unlike -mpreferred-stack-boundary=3, this will still align by 16 before making further calls. So if you have other non-leaf functions, you might want to just use an attribute on your hacky C _start() instead of affecting the whole file.
A worse, more hacky way would be to try putting
asm("push %rax"); at the very top of _start to modify RSP by 8, where GCC hopefully runs it before doing anything else with the stack. GNU C Basic asm statements are implicitly volatile so you don't need asm volatile, although that wouldn't hurt.
You're 100% on your own and responsible for correctly tricking the compiler by using inline asm that works for whatever optimization level you're using.
Another safer way would be write your own light-weight _start that calls main:
// at global scope:
asm(
".globl _start \n"
"_start: \n"
" mov (%rsp), %rdi \n" // argc
" lea 8(%rsp), %rsi \n" // argv
" lea 8(%rsi, %rdi, 8), %rdx \n" // envp
" call main \n"
// NOT DONE: stdio cleanup or other atexit stuff
// DO NOT USE WITH GLIBC; use libc's CRT code if you use libc
" mov %eax, %edi \n"
" mov $231, %eax \n"
" syscall" // exit_group( main() )
);
int main(int argc, char**argv, char**envp) {
... your code here
return 0;
}
If you didn't want main to return, you could just pop %rdi; mov %rsp, %rsi ; jmp main to give it argc and argv without a return address.
Then main can exit via inline asm, or by calling exit() or _exit() if you link libc. (But if you link libc, you should usually use its _start.)
See also: How Get arguments value using inline assembly in C without Glibc? for other hand-rolled _start versions; this is pretty much like #zwol's there.
This question already has answers here:
Why isn't sizeof for a struct equal to the sum of sizeof of each member?
(13 answers)
Closed 6 years ago.
I would like to understand the results got with "gcc -m32" and "gcc -m64" compilation on the following small code:
#include <stdio.h>
#include <stdlib.h>
int main() {
struct MixedData
{
char Data1;
short Data2;
int Data3;
char Data4;
};
struct X {
char c;
uint64_t x;
};
printf("size of struct MixedData = %zu\n", sizeof(struct MixedData));
printf("size of struct X = %zu\n", sizeof(struct X));
printf("size of uint64_t = %zu\n", sizeof(uint64_t));
return 0;
}
With "gcc -m32", the ouput is :
size of struct MixedData = 12
size of struct X = 12
size of uint64_t = 8
Is size of struct X equal to 12 because compiler sets the following padding?
struct X {
char c; // 1 byte
char d[3]; // 3 bytes
uint64_t x; // 8 bytes
};
If this is the case, what's the size of a single word with 32 bits compilation (4 bytes?)? If it is equal to 4 bytes, this would be consistent because 12 is a multiple of 4.
Now concerning the size of MixedData with "gcc -m32" compilation, I get "size of struct MixedData = 12". I don't understand this value because I saw that total size of a structure had to be a multiple of the biggest size attribute in this structure. For example, here into structure MixedData, the biggest attribute is int Data3 with sizeof(Data3) = 4 bytes; why don't we have rather the following padding:
struct MixedData
{
char Data1; // 1 byte
char Datatemp1[3]; // 3 bytes
short Data2; // 2 bytes
short Data2temp; // 2 bytes
int Data3; // 4 bytes
char Data4; // 1 byte
char Data4temp[3] // 3 bytes
};
So the total size of struct MixedData would be equal to 16 bytes and not 12 bytes like I get.
Can anyone see what's wrong about these 2 interpretations?
A similar issue is about "gcc -m64" compilation; the output is:
size of struct MixedData = 12
size of struct X = 16
size of uint64_t = 8
The size of struct X (16 bytes) seems to be consistent because I think that compiler in 64 bits mode sets the following padding:
struct X {
char c; // 1 byte
char d[7]; // 7 bytes
uint64_t x; // 8 bytes
};
But I don't understand the value of struct MixedData (12 bytes). Indeed, I don't know how compiler sets the padding in this case because 12 is not a multiple of memory word in 64 bits mode (supposing this one is equal to 8 bytes). Could you tell me the padding generated by "gcc -m64" in this last case (for struct MixedData) ?
This one is a curiosity
struct
{
char Data1;
short Data2;
int Data3;
char Data4;
} x;
unsigned fun ( void )
{
x.Data1=1;
x.Data2=2;
x.Data3=3;
x.Data4=4;
return(sizeof(x));
}
Compiling then disassembling
64
0000000000000000 <fun>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: c6 05 00 00 00 00 01 movb $0x1,0x0(%rip) # b <fun+0xb>
b: 66 c7 05 00 00 00 00 movw $0x2,0x0(%rip) # 14 <fun+0x14>
12: 02 00
14: c7 05 00 00 00 00 03 movl $0x3,0x0(%rip) # 1e <fun+0x1e>
1b: 00 00 00
1e: c6 05 00 00 00 00 04 movb $0x4,0x0(%rip) # 25 <fun+0x25>
25: b8 0c 00 00 00 mov $0xc,%eax
2a: 5d pop %rbp
2b: c3 retq
32
00000000 <fun>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: c6 05 00 00 00 00 01 movb $0x1,0x0
a: 66 c7 05 02 00 00 00 movw $0x2,0x2
11: 02 00
13: c7 05 04 00 00 00 03 movl $0x3,0x4
1a: 00 00 00
1d: c6 05 08 00 00 00 04 movb $0x4,0x8
24: b8 0c 00 00 00 mov $0xc,%eax
29: 5d pop %ebp
2a: c3 ret
Understand that the m32 and m64 are perhaps poorly described, one is basically the 32 bit processor, 32 bit registers (ebx, eax, ax, ah but not rbx,rax) and the other 64 bit processor with 64 bit registers (rbx,ebx,bx,bh,bl)
There doesnt have to be a connection between the size of or construction of structs vs the instruction set chosen.
the interesting thing here is the size of the struct 1+2+4+1 = 8 so they could have done it in 8 bytes. Now they probably wanted the int aligned, so that would pad it by a byte, and perhaps they wanted the whole thing aligned on a 32 bit boundary adding 3 more so that is probably what happened. The 32 bit code does make this a bit clear, no only did they align the int they also aligned the short. So they pad between Data1 and Data2 to align Data2 on a 16 bit boundary then that makes Data3 aligned on a 32 bit boundary and Data3 is a byte so cant be unaligned. Pad the end to aligned the next thing in .data.
The 64 bit code looks broken, perhaps they want the linker to patch that one up.
00000000004004d6 <fun>:
4004d6: 55 push %rbp
4004d7: 48 89 e5 mov %rsp,%rbp
4004da: c6 05 57 0b 20 00 01 movb $0x1,0x200b57(%rip) # 601038 <x>
4004e1: 66 c7 05 50 0b 20 00 movw $0x2,0x200b50(%rip) # 60103a <x+0x2>
4004e8: 02 00
4004ea: c7 05 48 0b 20 00 03 movl $0x3,0x200b48(%rip) # 60103c <x+0x4>
4004f1: 00 00 00
4004f4: c6 05 45 0b 20 00 04 movb $0x4,0x200b45(%rip) # 601040 <x+0x8>
4004fb: b8 0c 00 00 00 mov $0xc,%eax
400500: 5d pop %rbp
400501: c3 retq
ahh, I see yes that is what they were doing. And that is what they did align both Data2 and Data3. I guess I should have made it generate the address to the whole struct...
struct
{
char Data1;
short Data2;
int Data3;
char Data4;
} x;
unsigned fun ( void )
{
unsigned long long z;
z=(unsigned long long)&x;
x.Data1=1;
x.Data2=2;
x.Data3=3;
x.Data4=4;
return(sizeof(x));
}
int main ( void )
{
fun();
}
producing
00000000004004d6 <fun>:
4004d6: 55 push %rbp
4004d7: 48 89 e5 mov %rsp,%rbp
4004da: 48 c7 45 f8 38 10 60 movq $0x601038,-0x8(%rbp)
4004e1: 00
4004e2: c6 05 4f 0b 20 00 01 movb $0x1,0x200b4f(%rip) # 601038 <x>
4004e9: 66 c7 05 48 0b 20 00 movw $0x2,0x200b48(%rip) # 60103a <x+0x2>
4004f0: 02 00
4004f2: c7 05 40 0b 20 00 03 movl $0x3,0x200b40(%rip) # 60103c <x+0x4>
4004f9: 00 00 00
4004fc: c6 05 3d 0b 20 00 04 movb $0x4,0x200b3d(%rip) # 601040 <x+0x8>
400503: b8 0c 00 00 00 mov $0xc,%eax
400508: 5d pop %rbp
400509: c3 retq
confirming the base address 0x60138.
The struct is not tied to the instruction set. Change to this
struct
{
char Data1;
short Data2;
int Data3;
char Data4;
} __attribute__((packed)) x;
unsigned fun ( void )
{
unsigned long long z;
z=(unsigned long long)&x;
x.Data1=1;
x.Data2=2;
x.Data3=3;
x.Data4=4;
return(sizeof(x));
}
int main ( void )
{
fun();
}
and we get this
00000000004004d6 <fun>:
4004d6: 55 push %rbp
4004d7: 48 89 e5 mov %rsp,%rbp
4004da: 48 c7 45 f8 38 10 60 movq $0x601038,-0x8(%rbp)
4004e1: 00
4004e2: c6 05 4f 0b 20 00 01 movb $0x1,0x200b4f(%rip) # 601038 <x>
4004e9: 66 c7 05 47 0b 20 00 movw $0x2,0x200b47(%rip) # 601039 <x+0x1>
4004f0: 02 00
4004f2: c7 05 3f 0b 20 00 03 movl $0x3,0x200b3f(%rip) # 60103b <x+0x3>
4004f9: 00 00 00
4004fc: c6 05 3c 0b 20 00 04 movb $0x4,0x200b3c(%rip) # 60103f <x+0x7>
400503: b8 08 00 00 00 mov $0x8,%eax
400508: 5d pop %rbp
400509: c3 retq
the size of the struct is now 8 bytes, and they generated unaligned accesses.
I have tried the following code on gcc 4.4.5 on Linux and gcc-llvm on Mac OSX(Xcode 4.2.1) and this. The below are the source and the generated disassembly of the relevant functions. (Added: compiled with gcc -O2 main.c)
#include <stdio.h>
__attribute__((noinline))
static void g(long num)
{
long m, n;
printf("%p %ld\n", &m, n);
return g(num-1);
}
__attribute__((noinline))
static void h(long num)
{
long m, n;
printf("%ld %ld\n", m, n);
return h(num-1);
}
__attribute__((noinline))
static void f(long * num)
{
scanf("%ld", num);
g(*num);
h(*num);
return f(num);
}
int main(void)
{
printf("int:%lu long:%lu unsigned:%lu\n", sizeof(int), sizeof(long), sizeof(unsigned));
long num;
f(&num);
return 0;
}
08048430 <g>:
8048430: 55 push %ebp
8048431: 89 e5 mov %esp,%ebp
8048433: 53 push %ebx
8048434: 89 c3 mov %eax,%ebx
8048436: 83 ec 24 sub $0x24,%esp
8048439: 8d 45 f4 lea -0xc(%ebp),%eax
804843c: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
8048443: 00
8048444: 89 44 24 04 mov %eax,0x4(%esp)
8048448: c7 04 24 d0 85 04 08 movl $0x80485d0,(%esp)
804844f: e8 f0 fe ff ff call 8048344 <printf#plt>
8048454: 8d 43 ff lea -0x1(%ebx),%eax
8048457: e8 d4 ff ff ff call 8048430 <g>
804845c: 83 c4 24 add $0x24,%esp
804845f: 5b pop %ebx
8048460: 5d pop %ebp
8048461: c3 ret
8048462: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048469: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi
08048470 <h>:
8048470: 55 push %ebp
8048471: 89 e5 mov %esp,%ebp
8048473: 83 ec 18 sub $0x18,%esp
8048476: 66 90 xchg %ax,%ax
8048478: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
804847f: 00
8048480: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048487: 00
8048488: c7 04 24 d8 85 04 08 movl $0x80485d8,(%esp)
804848f: e8 b0 fe ff ff call 8048344 <printf#plt>
8048494: eb e2 jmp 8048478 <h+0x8>
8048496: 8d 76 00 lea 0x0(%esi),%esi
8048499: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi
080484a0 <f>:
80484a0: 55 push %ebp
80484a1: 89 e5 mov %esp,%ebp
80484a3: 53 push %ebx
80484a4: 89 c3 mov %eax,%ebx
80484a6: 83 ec 14 sub $0x14,%esp
80484a9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
80484b0: 89 5c 24 04 mov %ebx,0x4(%esp)
80484b4: c7 04 24 e1 85 04 08 movl $0x80485e1,(%esp)
80484bb: e8 94 fe ff ff call 8048354 <__isoc99_scanf#plt>
80484c0: 8b 03 mov (%ebx),%eax
80484c2: e8 69 ff ff ff call 8048430 <g>
80484c7: 8b 03 mov (%ebx),%eax
80484c9: e8 a2 ff ff ff call 8048470 <h>
80484ce: eb e0 jmp 80484b0 <f+0x10>
We can see that g() and h() are mostly identical except the & (address of) operator beside the argument m of printf()(and the irrelevant %ld and %p).
However, h() is tail-call optimized and g() is not. Why?
In g(), you're taking the address of a local variable and passing it to a function. A "sufficiently smart compiler" should realize that printf does not store that pointer. Instead, gcc and llvm assume that printf might store the pointer somewhere, so the call frame containing m might need to be "live" further down in the recursion. Therefore, no TCO.
It's the & that does it. It tells the compiler that m should be stored on the stack. Even though it is passed to printf, the compiler has to assume that it might be accessed by somebody else and thus must the cleaned from the stack after the call to g.
In this particular case, as printf is known by the compiler (and it knows that it does not save pointers), it could probably be taught to perform this optimization.
For more info on this, look up 'escape anlysis'.
Suppose I have two files:
file1.c- contains global definition of an int array of size 10 named "array[10]".
file2.c- contains an int pointer named "extern int *array", here I am trying to link this pointer to array.
But when I check the address of array in file1.c and pointer value in file2.c, they are both different. Why it is happening?
That doesn't work, in file2.c, you need
extern int array[];
since arrays and pointers are not the same thing. Both declarations must have the compatible types, and int* is not compatible with int[N].
What actually happens is not specified, the programme is ill-formed with extern int *array;, but probably, the first sizeof(int*) bytes of the array are interpreted as an address.
extern1.c
#include <stdio.h>
extern int *array;
int test();
int main(int argc, char *argv[])
{
printf ("in main: array address = %x\n", array);
test();
return 0;
}
extern2.c
int array[10] = {1, 2, 3};
int test()
{
printf ("in test: array address = %x\n", array);
return 0;
}
The output:
in main: array address = 1
in test: array address = 804a040
And the assemble code:
08048404 <main>:
8048404: 55 push %ebp
8048405: 89 e5 mov %esp,%ebp
8048407: 83 e4 f0 and $0xfffffff0,%esp
804840a: 83 ec 10 sub $0x10,%esp
804840d: 8b 15 40 a0 04 08 mov 0x804a040,%edx <--------- this (1)
8048413: b8 20 85 04 08 mov $0x8048520,%eax
8048418: 89 54 24 04 mov %edx,0x4(%esp)
804841c: 89 04 24 mov %eax,(%esp)
804841f: e8 dc fe ff ff call 8048300 <printf#plt>
8048424: e8 07 00 00 00 call 8048430 <test>
8048429: b8 00 00 00 00 mov $0x0,%eax
804842e: c9 leave
804842f: c3 ret
08048430 <test>:
8048430: 55 push %ebp
8048431: 89 e5 mov %esp,%ebp
8048433: 83 ec 18 sub $0x18,%esp
8048436: c7 44 24 04 40 a0 04 movl $0x804a040,0x4(%esp) <------- this (2)
804843d: 08
804843e: c7 04 24 3d 85 04 08 movl $0x804853d,(%esp)
8048445: e8 b6 fe ff ff call 8048300 <printf#plt>
804844a: b8 00 00 00 00 mov $0x0,%eax
804844f: c9 leave
8048450: c3 ret
Pay attention to the <------- in the assemble code. You can see in main function the array is array[0] and in test function the array is the address.