-4

My Q: Was it possible in older compilers?

Creating an ordinary pointer to a const variable in C. I saw some ytuber (2016), but now the code doesn't compile.

Following code now gives error (which is OK)!

#include <stdio.h>
int main()
{
    const int a = 10;
    int *ptr = &a; //compile error source.c:6:16: warning: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
    *ptr = 50;
    printf("Value of 'a' is %d", a);
    return 0;
}
23
  • 5
    Please choose one language you are asking about. C and C++ are different languages and answers may differ. Commented Mar 24 at 9:58
  • 2
    "(Please define behaviours in both C/C++, if they differ)" Please ask two separate questions, one for C and one for C++. Commented Mar 24 at 10:00
  • I would like to learn behaviours of the snippet in both C/C++. Commented Mar 24 at 10:00
  • 1
    sure, but this could be two separate questions ("Ordinary pointer to const variable in C" on one post and "Ordinary pointer to const variable in C++" in another later) Commented Mar 24 at 10:02
  • "Following code now gives error" How do you compile it? It works with a warning in C. It's an error in C++. "Creating an ordinary pointer to a const variable in C. I saw some ytuber (2016), but now the code doesn't compile." If it's C code compiled with a C compiler, it should work. Commented Mar 24 at 10:02

2 Answers 2

6

My Q: Was it possible in older compilers?

It was possible to compile this code in older C compilers and remains possible today. It compiles with a warning and, I expect, always did in any compiler that supported const.

The initialization of int *ptr with &a, where a is a const int, violates the constraints in C 2024 6.5.17.2. Those constraints limit the combinations of types of the left and right operands of assignment (whose rules are also used for initialization). They include assigning a pointer value to a pointer when the type the left operand points to has all the qualifiers that the type the right operand points to but exclude cases where the right pointed-to type has a qualifier the left pointed-to type does not have.

When there is a constraint violation, C 2024 5.2.1.3 requires a C implementation to produce a diagnostic message. However, a C implementation may still accept and translate (compile and link) a program with a constraint violation. 5.2.1.3 acknowledges “It can also successfully translate an invalid program.”

The above have been essentially true, with varying language, in all versions of the ANSI or ISO C standard, although the explicit language about translating an invalid program is absent in the 1990 version.

Largely due to the history of C, that it was in diverse use and growing and developing as the C standard was being created and updated, many compilers are by default lax in what they accept. This is so that they are accommodating to diverse uses of C and inclusion of old code that was developed under different rules and practices for the language. And thus common compilers will by default issue a diagnostic for int *ptr = &a; but accept it. You can usually change this with a switch such as -Werror (Clang and GCC) or /WX (MSVC) which tells the compiler to treat all warnings as errors. (This can actually put the compiler in a mode that does not conform to standard C, as it may cause the compiler to reject programs that are actually defined by the C standard, depending on the selection of warnings that are enabled.)

C++ developed later, after const was well established, and there was not so much legacy code to support. So C++ compilers tend to be stricter and reject int *ptr = &a; by default. That is, they issue an error for it rather than merely a warning.

The above speaks to int *ptr = &a;. With *ptr = 50;, we have another problem. The C standard does not require a diagnostic for this because it is, by itself, a defined statement: It assigns 50 to an lvalue of type int, with no const involved. The problem occurred earlier, when ptr was initialized. However, neither the C nor the C++ standard defines how the program will behave when this statement is executed. It attempts to assign a value to an object that was defined with const, and that is not defined. (C 2024 6.7.4: “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.”)

It is possible a C implementation will execute this program as if a were not defined with const, it is possible a C implementation will execute this program as if a were permanently fixed at its initial value, and it is possible a C implementation will detect the problem and reject the program. In more detail:

  • If the program is compiled with no optimization, and the compiler does little analysis of the program, it may execute *ptr = 50; by storing 50 in the place where ptr points, which is the memory for a. This memory is likely not read-only, because the common implementations of automatic variables, even const ones, is to assign them memory in the stack, and that memory must be read-write for normal program operation.
  • If optimization is turned on and the compiler does more analysis of the program, it may see that *ptr is being assigned a value after ptr has been assigned the address of a const int, and so the compiler may issue an error that it did not issue without optimization.
  • If the compiler does not issue that error, then optimization may see that *ptr = 50; assigns 50 to an object that is immediately printed, so it may optimize the printf to print “Value of 'a' is 50”, without ever changing the memory of a. Alternately, it may see that the printf prints the value of a const object that was initialized to 10 and optimize the printf to print “Value of 'a' is 10”.

In summary, diverse outcomes are possible, and none are guaranteed by the C standard. So, to answer your question, “Was it possible in older compilers?”, yes, it was possible, but it was not something you should rely on.

Sign up to request clarification or add additional context in comments.

1 Comment

Rather than -Werror we can use -pedantic-errors. Then it should only give errors for syntax and constraint violations without turning other forms of warnings into errors too.
4

The code is invalid C and invalid C++. You cannot assign a non-const pointer to a const qualified variable.

From the C23 standard:

6.5.17.2 Simple assignment
Constraints

One of the following shall hold:
/--/

  • the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left operand has all the qualifiers of the type pointed to by the right operand;

What this means in plain English: both pointers must point at the same type and the pointer which is the left operand of = must have all qualifiers such as const as the pointer which is the right operand.

C++ is mostly the same except it also allows operator overloading of the assignment operator for special cases.

Normative text below "constraints" in the C standard is mandatory for the compiler to conform to: it must show the programmer a diagnostic message if there are constraint or syntax violations in the compiled code. (C++ might sometimes call non-conforming code for ill-formed.) It could be a warning or an error - see What must a C compiler do when it finds an error? for details.


Was it possible in older compilers?

No, this code was never valid. The oldest standard called C90 had about the same text:

6.3.16.1 Simple assignment
Constraints

One of the following shall hold:
/--/

  • both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

You could make it valid C by introducing a dirty cast: int *ptr = (int*)&a;. But this will invoke various forms of undefined behavior, so it is a really bad idea. For example from C23 6.7.4:

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.

const int a = 10;
int *ptr = (int*)&a;
*ptr = 42;  // BAD, undefined behavior as per C23 6.7.4

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.