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.