0

First file:

//a.c

const int i = 9;   //Just contains the variable definition

Second file:

//b.c

#include<stdio.h>

extern int i;

int main()
{
    int *ptr = &i;

    printf("Before: %d\n",i);

    *ptr = 99;

    printf("After:  %d\n",i);

    return 0;
}

I was expecting const qualifier error/warning at extern int i; and read-only memory modification error in *ptr = 99; in b.c, but instead the compilation went fine.

1st print statement printed good but the second one gave Segmentation fault.

Why didn't it crash in the compilation stage with the anticipated errors and why it gave segmentation fault instead?

NOTE - GCC 8.3.0

Agrudge Amicus
  • 1,033
  • 7
  • 19

3 Answers3

6

Attempting to write to a const int through a pointer is undefined behavior per the standard.

What's probably happening in practice is that your variable is placed in the executable's .rodata region - which is marked read-only in memory - and when you attempt to write to it through the pointer it triggers a page fault that crashes the program.


Removing the const should fix this, if you need to write to the variable.

Daniel Kleinstein
  • 5,262
  • 1
  • 22
  • 39
6

Translation units are compiled separately. Therefore the compiler has no idea that i is const in some other file. It trusts the declaration which is

extern int i;

There is no const there. That is why you see no warning.

To make sure that the similar error are detected early, it's advised to put declarations of global variables to a header file and let both a.c and b.c include this header.

In such a case the compiler would generate a diagnostic while compiling a.c because the declaration and the definition would be incompatible.

... a.h
#pragma once
extern int i;

... a.c
#include "a.h"
const int i = 9;

... b.c
#include<stdio.h>
#include "a.h"

int main()
{
    int *ptr = &i;

    printf("Before: %d\n",i);

    *ptr = 99;

    printf("After:  %d\n",i);

    return 0;
}

Compilation command:

gcc a.c b.c

Error:

a.c:3:11: error: conflicting type qualifiers for ‘i’
    3 | const int i = 9;
      |           ^
In file included from a.c:1:
a.h:2:12: note: previous declaration of ‘i’ was here
    2 | extern int i;
      |            ^


Edit

While compiling a.c the compiler assumes that the object is constant and it is put into read-only memory. On Linux machines it will be put .rodata section that will be mapped to process' address space without PROT_WRITE flag.

When the memory is modified in b.c then segmentation fault exception (invalid access to memory) is raised and the program crashes.

Note that modifying a constant object triggers Undefined Behavior. The program could run for years and then suddenly start crashing.

tstanisl
  • 13,520
  • 2
  • 25
  • 40
  • This does not explain why the program crashes (and indeed is not the reason why it crashes) – Daniel Kleinstein Sep 14 '21 at 12:17
  • @DanielKleinstein, I guess the question was why the invalid code compiles successfully. My answer should help OP avoid similar issues in future. – tstanisl Sep 14 '21 at 12:23
1

"I was expecting const qualifier error/warning at extern int i; and read-only memory modification error in *ptr = 99; in b.c, but instead the compilation went fine."

a.b and b.c, although each as stand-alone are syntactically correct, each occupies its own compilation unit, with nothing tying their contents together. Their is nothing to cause a compile conflict because the contents of one have zero visibility of the contents of the other.

As written,

const int i = 9;//in a.c

and

extern int i;//in b.c

occupy two completely autonomous places in memory,and although they share the same symbol, have no relationship whatsoever, except that at run-time they are participants within the same process. (if one is not optimized out.)

It is unclear whether your question derives from the need to deploy actual code, or just as a learning project. Either way, when it is desired to have a project global variable, a proper way to do it (some would say idiomatic way) is to:

  • declare an extern in a header file
  • #include that header file in in any .c file that will Use it
  • Finally, define it in global space once in one of the .c files that will use it.

Eg:

a.h

extern int i; //declare here   

a.c

#include "a.h"
...
int i = 0;//define in global space and use here 
          //(often in file containing main() function)

b.c

   #include "a.h"
   ...
   int *ptr = &i; //use here

Now you have a single instance of globally visible variable that can be accessed from both a.c and b.c, As such, the potential for conflict now exists and because both .c files can see and act on the single instance of i, the way it is declared and defined will be scrutinized differently during compilation, across all compilation units. Inconsistencies will be seen and flagged.

ryyker
  • 22,849
  • 3
  • 43
  • 87