C optimization: Why does the compiler treat an object not as constant? - c

Compiling the following C module
static const int i = 1;
void f (const int *i);
int g (void)
{
f (&i);
return i;
}
using gcc -S -O3 on an x86_64 maching yields the following assembly for the function g:
g:
leaq i(%rip), %rdi
subq $8, %rsp
call f#PLT
movl $1, %eax # inlined constant as an immediate
addq $8, %rsp
ret
In other words, the return statement is compiled to moving the constant $1 into the return register %eax, which makes sense because i is declared constant.
However, if I remove that const so that I have
static int i = 1;
void f (const int *i);
int g (void)
{
f (&i);
return i;
}
the output of gcc -S -O3 suddenly becomes:
g:
leaq i(%rip), %rdi
subq $8, %rsp
call f#PLT
movl i(%rip), %eax # reload i
addq $8, %rsp
ret
That is, the return value is explicitly loaded from memory after the call to f.
Why is this so? The argument to f is declared to be a pointer to a constant int, so f should not be allowed to alter i. Furthermore, f cannot call a function that modifies i through a non-const reference because the only such function could be g as i is declared static.

It is not undefined behavior to cast a pointer to const to a pointer to non-const and modify the referenced object, as long as the referenced object is not declared const.
6.7.3p6 says: "If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined."

Changes the situation. Body is known. No special actions are required.
static int i = 1;
__attribute__((noinline)) void f (int *i)
{
*i *=2;
}
int g (void)
{
f (&i);
return i;
}
f:
sal DWORD PTR [rdi]
ret
g:
mov edi, OFFSET FLAT:i
call f
mov eax, DWORD PTR i[rip]
ret
i:

Related

why is not const char put in rodata segment?

Having this:
#include <stdio.h>
#include <stdlib.h>
void f(const char *str){
char *p = (char*)str;
*p=97;
}
int main(){
char c;
f(&c);
char *p = malloc(10);
if (p) { f(p); printf("p:%s\n",p); free(p); }
const char d = 0; //only this part in interest
f(&d); // here the function modifies the the char, but since it is NOT in rodata, no problem
printf("d:%c\n",d);
printf("c:%c\n",c);
}
Will generate gas:
...
.L3:
# a.c:16: const char d = 0;
movb $0, -10(%rbp) #, d
# a.c:17: f(&d);
leaq -10(%rbp), %rax #, tmp98
movq %rax, %rdi # tmp98,
call f #
# a.c:18: printf("d:%c\n",d);
movzbl -10(%rbp), %eax # d, d.0_1
movsbl %al, %eax # d.0_1, _2
movl %eax, %esi # _2,
leaq .LC1(%rip), %rdi #,
movl $0, %eax #,
call printf#PLT #
# a.c:20: printf("c:%c\n",c);
...
Here, the d const char variable is only moved to stack, but its name (rip location) is not in .section .rodata, why is that? When it has const modifier. Being it char* string, then it is placed automatically on rodata (char* does not even need const modifier). I have read somewhere constness is inherited (meaning once a variable is declared with const modifier, then even casting that cause cast-away-constness, does not change the constness - i.e. it will remain). But here the const char modifier is not even taken into account (directly manipulated via stack, as arrays are). Why?
The variable d is not static, but a function-local variable. If the function containing it is called multiple times (recursively, or concurrently in multiple threads), you get multiple instances of the variable (within the stack frame of the function), each of which has its own individual address, even though all of them contain the same data. The C standard requires these instances to be distinct. If you define the variable as static, the compiler might move it into the .rodata section, so that you only get one instance.
String literals (e.g. "foo") however are not required to have individual addresses when they appear in (recursive) functions (unless they are used to initialize a char array), so the compiler usually places them into a .rodata section.

GCC: Optimizing away memory loads and stores

EDIT 1: Added another example (showing that GCC is, in principle, be capable to do what I want to achieve) and some more discussion at the end of this question.
EDIT 2: Found the malloc function attribute, which should do what. Please take a look at the very end of the question.
This is a question about how to tell the compiler that stores to a memory area are not visible outside of a region (and thus could be optimized away). To illustrate what I mean, let's take a look at the following code
int f (int a)
{
int v[2];
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
gcc -O2 generates the following assembly code (x86-64 gcc, trunk, on https://godbolt.org):
f:
leal -1(%rdi), %edx
xorl %eax, %eax
testl %edi, %edi
jle .L4
.L3:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L3
ret
.L4:
ret
As one can see, the loads and stores into the array v are gone after optimization.
Now consider the following code:
int g (int a, int *v)
{
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
The difference is that v is not (stack-) allocated in the function, but provided as an argument. The result of gcc -O2 in this case is:
g:
leal -1(%rdi), %edx
movl $0, 4(%rsi)
xorl %eax, %eax
movl %edx, (%rsi)
testl %edi, %edi
jle .L4
.L3:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L3
movl %eax, 4(%rsi)
movl $-1, (%rsi)
ret
.L4:
ret
Clearly, the code has to store the final values of v[0] and v[1] in memory as they may be observable.
Now, what I am looking for is a way to tell the compiler that the memory pointed to by v in the second example isn't accessible any more after the function g has returned so that the compiler could optimize away the memory accesses.
To have an even simpler example:
void h (int *v)
{
v[0] = 0;
}
If the memory pointed to by v isn't accessible after h returns, it should be possible to simplify the function to a single ret.
I tried to achieve what I want by playing with the strict aliasing rules but haven't succeeded.
ADDED IN EDIT 1:
GCC seems to have the necessary code built-in as the following example shows:
include <stdlib.h>
int h (int a)
{
int *v = malloc (2 * sizeof (int));
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
The generated code contains no loads and stores:
h:
leal -1(%rdi), %edx
xorl %eax, %eax
testl %edi, %edi
jle .L4
.L3:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L3
ret
.L4:
ret
In other words, GCC knows that changing the memory area pointed to by v is not observable through any side-effect of malloc. For purposes like this one, GCC has __builtin_malloc.
So I can also ask: How can user code (say a user version of malloc) make use of this functionality?
ADDED IN EDIT 2:
GCC has the following function attribute:
malloc
This tells the compiler that a function is malloc-like, i.e., that the pointer P returned by the function cannot alias any other pointer valid when the function returns, and moreover no pointers to valid objects occur in any storage addressed by P.
Using this attribute can improve optimization. Compiler predicts that a function with the attribute returns non-null in most cases. Functions like malloc and calloc have this property because they return a pointer to uninitialized or zeroed-out storage. However, functions like realloc do not have this property, as they can return a pointer to storage containing pointers.
It seems to do what I want as the following example shows:
__attribute__ (( malloc )) int *m (int *h);
int i (int a, int *h)
{
int *v = m (h);
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
The generated assembler code has no loads and stores:
i:
pushq %rbx
movl %edi, %ebx
movq %rsi, %rdi
call m
testl %ebx, %ebx
jle .L4
leal -1(%rbx), %edx
xorl %eax, %eax
.L3:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L3
popq %rbx
ret
.L4:
xorl %eax, %eax
popq %rbx
ret
However, as soon as the compiler sees a definition of m, it may forget about the attribute. For example, this is the case when the following definition is given:
__attribute__ (( malloc )) int *m (int *h)
{
return h;
}
In that case, the function is inlined and the compiler forgets about the attribute, yielding the same code as the function g.
P.S.: Initially, I thought that the restrict keyword may help, but it doesn't seem so.
EDIT: Discussion about the noinline attribute added at the end.
Using the following function definition, one can achieve the goal of my question:
__attribute__ (( malloc, noinline )) static void *get_restricted_ptr (void *p)
{
return p;
}
This function get_restricted_ptr simply returns its pointer argument but informs the compiler that the returned pointer P cannot alias any other pointer valid when the function returns, and moreover no pointers to valid objects occur in any storage addressed by P.
The use of this function is demonstrated here:
int i (int a, int *h)
{
int *v = get_restricted_ptr (h);
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return;
}
The generated code does not contain loads and stores:
i:
leal -1(%rdi), %edx
xorl %eax, %eax
testl %edi, %edi
jle .L6
.L5:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L5
ret
.L6:
ret
ADDED IN EDIT: If the noinline attribute is left out, GCC ignores the malloc attribute. Apparently, in this case, the function gets inlined first so that there is no function call any more for which GCC would check the malloc attribute. (One can discuss whether this behaviour should be considered a bug in GCC.) With the noinline attribute, the function doesn't get inlined. Then, due to the malloc attribute, GCC understands that the call to that function is unnecessary and removes it completely.
Unfortunately, this means that the (trivial) function won't be inlined when its call is not eliminated due to the malloc attribute.
Both functions have side effects and memory reads & stores cannot be optimized out
void h (int *v)
{
v[0] = 0;
}
and
int g (int a, int *v)
{
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
The side effects have to be observable outside the function scope. Inline functions may have another behavior as the side effect might have to be observable outside the enclosing code.
inline int g (int a, int *v)
{
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
void h(void)
{
int x[2],y ;
g(y,x);
}
this code will be optimized to just a simple return
You can promise the compiler that nothing will happen to allow easier optimizations by using keyword restrict. But of course your code must keep this promise.
For C, the only restriction is that the compiler has to ensure that the code behaves the same. If the compiler can prove that the code behaves the same then it can and will remove the stores.
For example, I put this into https://godbolt.org/ :
void h (int *v)
{
v[0] = 0;
}
void foo() {
int v[2] = {1, 2};
h(v);
}
And told it to use GCC 8.2 and "-O3", and got this output:
h(int*):
mov DWORD PTR [rdi], 0
ret
foo():
ret
Note that there are two different versions of the function h() in the output. The first version exists in case other code (in other object files) want to use the function (and may be discarded by the linker). The second version of h() was inlined directly into foo() and then optimised down to absolutely nothing.
If you change the code to this:
static void h (int *v)
{
v[0] = 0;
}
void foo() {
int v[2] = {1, 2};
h(v);
}
Then it tells the compiler that the version of h() that only existed for linking with other object files isn't needed, so the compiler only generates the second version of h() and the output becomes this:
foo():
ret
Of course all optimizers in all compiler's aren't perfect - for more complex code (and for different compilers including different versions of GCC) results might be different (the compiler may fail to do this optimization). This is purely a limitation of the compiler's optimizer and not a limitation of C itself.
For cases where the compiler's optimiser isn't good enough, there are 4 possible solutions:
get a better compiler
improve the compiler's optimiser (e.g. send an email with to the compiler's developers that includes a minimal example and cross your fingers)
modify the code to make it easier for the compiler's optimiser (e.g. copy the input array into a local array, like "void h(int *v) { int temp[2]; temp[0] = v[0]; temp[1] = v[1]; ... ).
shrug and say "Oh, that's a pity" and do nothing

__attribute__((malloc)) vs restrict

Why does gcc need __attribute__((__malloc__))? Shouldn't the same info be communicatable by declaring malloc (and similar functions) as returning restricted pointers (void *restrict malloc(size_t))?
It seems to be that that approach would be better as apart from not requiring a nonstandard feature it would also allow one to apply it to functions "returning" via a pointer (int malloc_by_arg(void *restrict*retval, size_t size);).
Even being quite similar, same function yields different optimization when restrict or __attribute__((malloc)) are added. Considering this example (included here as a reference of a good example of __attribute__((malloc))):
#include <stdlib.h>
#include <stdio.h>
int a;
void* my_malloc(int size) __attribute__ ((__malloc__))
{
void* p = malloc(size);
if (!p) {
printf("my_malloc: out of memory!\n");
exit(1);
}
return p;
}
int main() {
int* x = &a;
int* p = (int*) my_malloc(sizeof(int));
*x = 0;
*p = 1;
if (*x) printf("This printf statement to be detected as unreachable
and discarded during compilation process\n");
return 0;
}
And this one (Same code without attributes):
void* my_malloc(int size);
int a;
void* my_malloc(int size)
{
void* p = malloc(size);
if (!p) {
printf("my_malloc: out of memory!\n");
exit(1);
}
return p;
}
int main() {
int* x = &a;
int* p = (int*) my_malloc(sizeof(int));
*x = 0;
*p = 1;
if (*x) printf("This printf statement to be detected as unreachable
and discarded during compilation process\n");
return 0;
}
As we could expect, the code with the malloc attribute is better optimized (both with -O3) than without it. Let me include just the differences:
without attribute:
[...]
call ___main
movl $4, (%esp)
call _malloc
testl %eax, %eax
je L9
movl $0, _a
xorl %eax, %eax
leave
.cfi_remember_state
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
L9:
.cfi_restore_state
movl $LC0, (%esp)
call _puts
movl $1, (%esp)
call _exit
.cfi_endproc
[...]
with attribute:
[...]
call ___main
movl $4, (%esp)
call _my_malloc
movl $0, _a
xorl %eax, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
[...]
Nonetheless, the use of restrict in that case is worthless, given that it doesn't optimize the generated code.If we modify the original code to be used with restrict :
void *restrict my_malloc(int size);
int a;
void *restrict my_malloc(int size)
{
void *restrict p = malloc(size);
if (!p) {
printf("my_malloc: out of memory!\n");
exit(1);
}
return p;
}
int main() {
int* x = &a;
int* p = (int*) my_malloc(sizeof(int));
*x = 0;
*p = 1;
if (*x) printf("This printf statement to be detected as unreachable and discarded \
during compilation process\n");
return 0;
}
The asm code is exactly the same than the generated without the malloc attribute:
[...]
call ___main
movl $4, (%esp)
call _malloc
testl %eax, %eax
je L9
movl $0, _a
xorl %eax, %eax
leave
.cfi_remember_state
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
L9:
.cfi_restore_state
movl $LC0, (%esp)
call _puts
movl $1, (%esp)
call _exit
.cfi_endproc
[...]
So for malloc/calloc-like functions, the use of __attribute__((__malloc__)) looks like more useful than restrict.
__attribute__((__malloc__)) and restrict have different behaviours to optimize the code, even being their definitions quite similar. That makes me think that there is no point to "merge" them, given that the compiler achieves different optimizations through different ways. Even when both are used at the same tiem, the generated code won't be more optimized than the most optimizated code with just one of them (__attribute__((__malloc__)) or restrict, depending on case). So is the programmer's choice to know which one fits better according to his/her code.
Why __attribute__((__malloc__)) is not standard? I don't know, but IMO, these similarities from the definition point of view, and differences from the behaviour point of view don't help to integrate both in the standard, with a clear, well differentiated and general speaking way.
in my test, even base on the function without attribute, it stall can optimize code with command: ~/gcc11.1.0-install/bin/aarch64-linux-gnu-gcc test2.c -O3 -S
main:
.LFB23:
.cfi_startproc
stp x29, x30, [sp, -16]!
.cfi_def_cfa_offset 16
.cfi_offset 29, -16
.cfi_offset 30, -8
mov w0, 4
mov x29, sp
bl my_malloc
adrp x1, .LANCHOR0
mov w0, 0
ldp x29, x30, [sp], 16
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
str wzr, [x1, #:lo12:.LANCHOR0]
ret
.cfi_endproc
.LFE23:
.size main, .-main

What's different between pointer with array in c? [duplicate]

This question already has answers here:
What is the difference between char s[] and char *s?
(14 answers)
Closed 5 years ago.
I try to google this topic, but no one can explain clear. I try the below code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char * argv[]){
char * p1 = "dddddd";
const char * p2 = "dddddd";
char p3[] = "dddddd";
char * p4 =(char*)malloc(sizeof("dddddd")+1);
strcpy(p4, "dddddd");
//*(p1+2) = 'b'; // test_1
//Output >> Bus error: 10
// *(p2+2) = 'b'; // test_2
// Output >> char_point.c:11:13: error: read-only variable is not assignable
*(p3+2) = 'b'; // test_3
// Output >>
//d
//dddddd
//dddddd
//ddbddd
*(p4+2) = 'k'; // test_4
// Output >>
//d
//dddddd
//dddddd
//ddbddd
//ddkddd
printf("%c\n", *(p1+2));
printf("%s\n", p1);
printf("%s\n", p2);
printf("%s\n", p3);
printf("%s\n", p4);
return 0;
}
I have try 3 tests, but only the test_3 and test_4 can pass. I know const char *p2 is read only, because it's a constant value! but i don't know why p1 can't be modified! which section of memory it's layout? BTW, I compile it on my Mac with GCC.
I try to compile it to dis-asm it by gcc -S, I got this.
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _main
.p2align 4, 0x90
_main: ## #main
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
subq $48, %rsp
movl $8, %eax
movl %eax, %ecx
leaq L_.str(%rip), %rdx
movl $0, -4(%rbp)
movl %edi, -8(%rbp)
movq %rsi, -16(%rbp)
movq %rdx, -24(%rbp)
movq %rdx, -32(%rbp)
movl L_main.p3(%rip), %eax
movl %eax, -39(%rbp)
movw L_main.p3+4(%rip), %r8w
movw %r8w, -35(%rbp)
movb L_main.p3+6(%rip), %r9b
movb %r9b, -33(%rbp)
movq %rcx, %rdi
callq _malloc
xorl %r10d, %r10d
movq %rax, -48(%rbp)
movl %r10d, %eax
addq $48, %rsp
popq %rbp
retq
.cfi_endproc
.section __TEXT,__cstring,cstring_literals
L_.str: ## #.str
.asciz "dddddd"
L_main.p3: ## #main.p3
.asciz "dddddd"
.subsections_via_symbols
I want to know every pointer what i declaration, which section is it?
"Why p1 can't be modified?"
Roughly speaking, p1 points to a string literal, and attempts to modify string literals cause undefined behavior in C.
More specifically, according to the §6.4.5 6 of the C11 Standard, string literals are:
used to initialize an array of static storage duration and length just sufficient to contain the sequence. For character string literals, the array elements have type char....
Concerning objects with static storage duration, §5.1.2 1 states that
All objects with static storage duration shall be initialized (set to their initial values) before program startup. The manner and timing of such initialization are otherwise unspecified.
"Which section of memory it's layout?"
But, the Standard does not specify any specific memory layouts that an implementation must follow.
What the Standard does say about the arrays of char which are created from string literals is that (§6.4.5 7):
It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.
So
char * p1 = "dddddd";
this should be
const char * p1 = "dddddd";
String literals (the ones in quotes) reside in read-only memory. Even if you
don't use the const keyword in the declaration of the variable, p1 still
points to read-only memory. So
*(p1+2) = 'b'; // test_1
is going to fail.
Here
*(p2+2) = 'b'; // test_2
// Output >> char_point.c:11:13: error: read-only variable is not assignable
the compiler tells you, you cannot do that because you declared p2 as const.
The difference between the first test and this one, is that the code tries to
modify a character and fails.
Now this:
char * p4 =(char*)malloc(sizeof("dddddd")+1);
First, do not cast malloc & friends. Second: the sizeof-operator returns the
number of bytes needed to store the expression in memory. "ddddd" is a string
literal, it returns a pointer to char, so sizeof("dddddd") returns the number
of bytes that a pointer to char needs to be stored in memory.
The correct function would be strlen:
char * p4 = malloc(strlen("dddddd")+1);
Note that in this case
char txt[] = "Hello world";
printf("%lu\n", sizeof(txt));
will print 12 and not 11. C strings are '\0'-terminated, that means that txt
holds all these characters plus the '\0'-terminating byte. In this case
sizeof doesn't return the number of bytes for a pointer, because txt is an
array.
void foo(char *txt)
{
printf("%lu\n", sizeof(txt));
}
void bar(void)
{
char txt[] = "Hello world";
foo(txt);
}
Here you won't get 12 like before, most probably 8 (today's common size for a
pointer). Even though txt in bar is an array, the txt in foo is a
pointer.
Arrays are constant pointer, which means that an array points to a memory address and you cant change were it points. But you can change the elements in it.
While you can change where the pointer points, but it's elements are constant.
for example consider this code
int main(){
int a[] = {1,2,3};
int * ptr = {1,2,3};
//a[0] == *(a+0)
//a[1] == *(a+1)
a += 1; // this is wrong, because we cant change were array points
ptr += 1; // this is correct, now the pointer ptr will points to the next element which is 2
a[0] += 2 // this is correct, now a[0] will become 3
*ptr += 2 // this is wrong, because we cant change the elements of the pointer.
return 0;
}

In which data segment is the C string stored?

I'm wondering what's the difference between char s[] = "hello" and char *s = "hello".
After reading this and this, I'm still not very clear on this question.
As I know, there are five data segments in memory, Text, BSS, Data, Stack and Heap.
From my understanding,
in case of char s[] = "hello":
"hello" is in Text.
s is in Data if it is a global variable or in Stack if it is a local variable.
We also have a copy of "hello" where the s is stored, so we can modify the value of this string via s.
in case of char *s = "hello":
"hello" is in Text.
s is in Data if it is a global variable or in Stack if it is a local variable.
s just points to "hello" in Text and we don't have a copy of it, therefore modifying the value of string via this pointer should cause "Segmentation Fault".
Am I right?
You are right that "hello" for the first case is mutable and for the second case is immutable string. And they are kept in read-only memory before initialization.
In the first case the mutable memory is initialized/copied from immutable string. In the second case the pointer refers to immutable string.
For first case wikipedia says,
The values for these variables are initially stored within the
read-only memory (typically within .text) and are copied into the
.data segment during the start-up routine of the program.
Let us examine segment.c file.
char*s = "hello"; // string
char sar[] = "hello"; // string array
char content[32];
int main(int argc, char*argv[]) {
char psar[] = "parhello"; // local/private string array
char*ps = "phello"; // private string
content[0] = 1;
sar[3] = 1; // OK
// sar++; // not allowed
// s[2] = 1; // segmentation fault
s = sar;
s[2] = 1; // OK
psar[3] = 1; // OK
// ps[2] = 1; // segmentation fault
ps = psar;
ps[2] = 1; // OK
return 0;
}
Here is the assembly generated for segment.c file. Note that both s and sar is in global aka .data segment. It seems sar is const pointer to a mutable initialized memory or not pointer at all(practically it is an array). And eventually it has an implication that sizeof(sar) = 6 is different to sizeof(s) = 8. There are "hello" and "phello" in readonly(.rodata) section and effectively immutable.
.file "segment.c"
.globl s
.section .rodata
.LC0:
.string "hello"
.data
.align 8
.type s, #object
.size s, 8
s:
.quad .LC0
.globl sar
.type sar, #object
.size sar, 6
sar:
.string "hello"
.comm content,32,32
.section .rodata
.LC1:
.string "phello"
.text
.globl main
.type main, #function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $64, %rsp
movl %edi, -52(%rbp)
movq %rsi, -64(%rbp)
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movl $1752326512, -32(%rbp)
movl $1869376613, -28(%rbp)
movb $0, -24(%rbp)
movq $.LC1, -40(%rbp)
movb $1, content(%rip)
movb $1, sar+3(%rip)
movq $sar, s(%rip)
movq s(%rip), %rax
addq $2, %rax
movb $1, (%rax)
movb $1, -29(%rbp)
leaq -32(%rbp), %rax
movq %rax, -40(%rbp)
movq -40(%rbp), %rax
addq $2, %rax
movb $1, (%rax)
movl $0, %eax
movq -8(%rbp), %rdx
xorq %fs:40, %rdx
je .L2
call __stack_chk_fail
.L2:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",#progbits
Again for local variable in main, the compiler does not bother to create a name. And it may keep it in register or in stack memory.
Note that local variable value "parhello" is optimized into 1752326512 and 1869376613 numbers. I discovered it by changing the value of "parhello" to "parhellp". The diff of the assembly output is as follows,
39c39
< movl $1886153829, -28(%rbp)
---
> movl $1869376613, -28(%rbp)
So there is no separate immutable store for psar . It is turned into integers in the code segment.
answer to your first question:
char s[] = "hello";
s is an array of type char. An array is a const pointer, meaning that you cannot change the s using pointer arithmetic (i.e. s++). The data aren't const, though, so you can change it.
See this example C code:
#include <stdio.h>
void reverse(char *p){
char c;
char* q = p;
while (*q) q++;
q--; // point to the end
while (p < q) {
c = *p;
*p++ = *q;
*q-- = c;
}
}
int main(){
char s[] = "DCBA";
reverse( s);
printf("%s\n", s); // ABCD
}
which reverses the text "DCBA" and produces "ABCD".
char *p = "hello"
p is a pointer to a char. You can do pointer arithmetic -- p++ will compile -- and puts data in read-only parts of the memory (const data).
and using p[0]='a'; will result to runtime error:
#include <stdio.h>
int main(){
char* s = "DCBA";
s[0]='D'; // compile ok but runtime error
printf("%s\n", s); // ABCD
}
this compiles, but not runs.
const char* const s = "DCBA";
With a const char* const, you can change neither s nor the data content which point to (i.e. "DCBE"). so data and pointer are const:
#include <stdio.h>
int main(){
const char* const s = "DCBA";
s[0]='D'; // compile error
printf("%s\n", s); // ABCD
}
The Text segment is normally the segment where your code is stored and is const; i.e. unchangeable. In embedded systems, this is the ROM, PROM, or flash memory; in a desktop computer, it can be in RAM.
The Stack is RAM memory used for local variables in functions.
The Heap is RAM memory used for global variables and heap-initialized data.
BSS contains all global variables and static variables that are initialized to zero or not initialized vars.
For more information, see the relevant Wikipedia and this relevant Stack Overflow question
With regards to s itself: The compiler decides where to put it (in stack space or CPU registers).
For more information about memory protection and access violations or segmentation faults, see the relevant Wikipedia page
This is a very broad topic, and ultimately the exact answers depend on your hardware and compiler.

Resources