2

So I was reading the book Language C by Kernighan Ritchie and on page 39, Chapter 2: Types, Operators and Expressions the author writes:

The const declaration can also be used with array arguments, to indicate that the function does not change that array:

int strlen(const char[]);

The result is implementation-defined if an attempt is made to change a const.

I don't understand what it means. Would appreciate if anyone could simplify what he means by that.

theFireman
  • 29
  • 3
  • As far as I can tell, there is no book “Language C by Kernighan Ritchie.” There is a well-known book called *The C programming Language* by Kernighan and Ritchie. The quote given appears on page 40, not 39, of the second edition. Because this book is famous, we were able to figure out the correct source in spite of the errors. However, you cannot expect that to always be the case. When giving a bibliographic citation, use the correct title, edition number, and page number. – Eric Postpischil Aug 07 '20 at 10:48
  • The quote is wrong. Attempting to modify an object defined with `const` is **undefined**, not **implementation-defined**, in both the 1990 C standard and the 2018 standard. And note that that rule applies only to objects **defined** with `const`; passing a pointer to a parameter that has `const` does not prevent the function from modifying the object (if it was not originally defined with `const`), although it must convert the pointer to a type without `const` to do so. – Eric Postpischil Aug 07 '20 at 11:01

5 Answers5

1

"Implementation defined" simply means that it is up to the implementation what should happen. A difference from "undefined behavior" is that when it is "implementation defined", the behavior needs to be documented. Read more about that here: Undefined, unspecified and implementation-defined behavior

But you can change things via a const pointer if you cast it to non-const. This will print 42;

void foo(const int *x)
{
    *(int *)x = 42;
}

int main(void)
{
    int n = 69;
    foo(&n);
    printf("%d\n", &n);
}

I wrote a related answer about const that you can read here: https://stackoverflow.com/a/62563330/6699433

klutt
  • 30,332
  • 17
  • 55
  • 95
  • Re “"Implementation defined" simply means that it is up to the implementation what should happen”: “Implementation-defined” means the C standard says the implementation **must document** what happens. – Eric Postpischil Aug 06 '20 at 08:25
  • 2
    @EricPostpischil Yes? Did you read the next sentence? – klutt Aug 06 '20 at 08:26
  • 2
    Re “But you can change things even if they are declared as const”: You can cast away constness and modify an object received via a pointer to const if the object was not **defined** with const. – Eric Postpischil Aug 06 '20 at 08:28
  • The second sentence is correct. That does not make the first sentence true. It is, at best, sloppy writing. (a) The first sentence is false. (b) The word “simply” takes emphasizes the falsehood and takes the sentence a step further from the truth. (c) There is no reason for it to be false. The statement could easily be reworded to be true. The C standard’s phrasing is not unclear: “unspecified behavior where each implementation documents how the choice is made.” … – Eric Postpischil Aug 06 '20 at 21:36
  • … You may reason that you can reconcile the statements, but that is because you know what the C standard says. A student encountering these assertions for the first time does not have that basis for resolving the conflict. – Eric Postpischil Aug 06 '20 at 21:37
  • As it turns out, Kernighan and Ritchie were wrong. The behavior is undefined, not implementation-defined. – Eric Postpischil Aug 07 '20 at 11:31
0

Declaring function parameters as const indicates that the function should not change the value of those parameters.

Even though C passes arguments by value, pointed-to values are susceptible to change. By declaring the function parameter as const, if the function attempts to modify the pointed-to value, the compiler will generate an error.

The following function will change the value pointed to by x:

void foo(int *x) 
{
  *x = 100;
}

In the following function, by marking the parameter as const, the function will not be able to change the value pointed to by x.

void foo(const int *x) 
{
  *x = 100; // Compiler generates an error
}

In C, even though it looks like you're passing an array when using the square brackets [], you're actually passing a pointer. So void foo(const int *x) would be the same as void foo(const int x[])

Link
  • 120
  • 1
  • 8
  • Suggestion: "indicates _to the compiler_ that the function _should not_ change.." Probably, with certain warnings turned off, the compiler would happily generate code that would change the value. – Paul Ogilvie Aug 06 '20 at 08:07
  • @PaulOgilvie And with casting you bypass warnings even if they are turned on – klutt Aug 06 '20 at 08:18
  • Re “if the function attempts to modify the pointed-to value, the compiler will generate an error”: If the function attempts to modify the pointed-to value by directly using the pointer with the `const` qualifier, the compiler should report an error. However, a function may use a cast to remove the `const` qualifier and attempt to modify the object through the new pointer, and the compiler generally should not generate an error for this; it is allowed by the C standard, needed in some situations, and can have fully defined behavior. – Eric Postpischil Aug 06 '20 at 21:45
  • Re “the function will not be able to change the value pointed to by x”: This is an excessively strong statement. Per my comment above, the function may be able to change the pointed-to object. In C, `const` on parameters is largely advisory and an aid to avoiding inadvertent modifications to the pointed-to objects. It is not enforced on objects not **defined** with `const` and may or may not be enforced on those defined with `const`, depending on the C implementation. – Eric Postpischil Aug 06 '20 at 21:47
0

Summary

Kernighan and Ritchie are wrong; attempting to modify const objects is undefined, not implementation-defined.

This rules applies only to objects originally defined with const.

const on function parameters is advisory, not enforced. It is possible, and defined by the C standard, for a function to modify an object pointed to with a const parameter if that object was not defined with const.

Details

The quoted passage is wrong. Attempting to modify an object defined with const has undefined behavior, not implementation-defined behavior. And this applies only to objects defined with const, not to objects passed via const-qualified pointers, if those objects were not originally defined with const.

C 2018 6.7.3 7 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.

The same wording appears in C 1990 6.5.3.

“Undefined“ means the C standard does not impose any requirements on the behavior (C 2018 3.4.3). This is different from “implementation-defined,” which means the C implementation must document how a choice among possibilities is made (C 2018 3.4.1).

Note that that rule applies only to objects defined with const. 6.7 5 tells us that, for object identifiers, a definition is a declaration that causes storage to be reserved for the object. If we declare int x; inside a function, that will cause storage to be reserved for x, so it is a definition. However, the statement int strlen(const char[]); merely declares a function and its parameter type. The actual parameter is not declared because there is no name for it. If we consider the actual function definition, such as:

int strlen(const char s[])
{
   …
}

then this function definition includes a declaration of the parameter s. And it does define s; storage for the parameter itself will be reserved when the function executes. However, this s is only a pointer to some object that the caller passes the address of. So this is not a definition of that object.

So far, we know the rule in 6.7.3 7 tells us that modifying an object defined with const has undefined behavior. Are there any other rules about a function modifying an object it has received through a pointer with const? There are. The left operand of an assignment operator must be modifiable. C 2018 6.5.16 2 says:

An assignment operator shall have a modifiable lvalue as its left operand.

An lvalue qualified with const is not modifiable, per C 2018 6.3.2.1 1. This paragraph is a constraint in the C standard, which means a C implementation is required to diagnose violations. (So, again, this is not implementation-defined behavior. The C implementation must produce a message.) The ++ and -- operators, both pre- and post-, have similar constraints.

So, a function with a parameter const char s[] cannot directly modify *s or s[i], at least not without getting a diagnostic message. However, a program is allowed to remove const in a conversion operator if it was not originally present. C 2018 6.3.2.3 2 says we can add const:

For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.

and then C 2018 6.3.2.3 7 says that, after we have done that, we can convert the const version back to the original type:

A pointer to an object type may be converted to a pointer to a different object type… when converted back again, the result shall compare equal to the original pointer.

What this means is that if a calling routine has:

int x = 3;
foo(&x);
printf("%d\n", x);

and foo is:

void foo(const int *p)
{
    * (int *) p = 4;
}

then this is allowed and defined by the C standard. The function foo removes const and modifies the object it points to, and “4” will be printed.

A lesson here is that const in function parameters is advisory, not enforced by C. It serves two purposes:

  • const on a function parameter is generally an indication for humans that the function will not modify the pointed-to object through that parameter. (However, there are circumstances, not discussed here, where this indication does not hold.)
  • The compiler will enforce a rule that the pointed-to object cannot be modified through the const type. This prevents inadvertent errors where a typographical error might result in an unwanted assignment to a const object. However, a function is permitted to explicitly remove const and then attempt to modify the object.
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
-1

const char[] as parameter type of a function is in fact equal to a pointer to const char (type const char *). The array notation was invented for convenience when passing pointer to arrays.

Related posts:

With declaring const char p_a[] you declare p_a as a pointer to const char.

The const qualifier associated to char tells the compiler that the char object/array pointed to in the caller shouldn't be modified.

Any attempt to modify this object/array with a non-const pointer/lvalue invokes undefined behavior:

"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."

Source: C18, 6.7.3/7

and a compiler usually will warn you when doing so.

strlen does not need to and also should not modify a string to which a pointer is passed as argument. For the sake of not giving any chance to accidentally modifying the string in the caller, the pointer is classified as pointer to const char.

The const qualifier adds an extra layer of security.

  • This sentence is false: “The `const` qualifier… tells the compiler that the object pointed to in the caller can't be modified.” This sentence is false (the citation from the C standard says something different): “Also when compiled the code has undefined behavior:” This sentence is dubious: “Any attempt to modify this object in the caller causes high-probably then a diagnostic at compiling.” Essentially, `const` on a non-definition declaration is advisory. A compiler will diagnose attempts to modify an object through a `const` lvalue, but a function may remove `const` via a cast. – Eric Postpischil Aug 07 '20 at 10:42
-1

That material appears a bit obsolete.

The strlen standard library function returns size_t nowadays, but anyway:

int strlen(const char[]);, which is the same as, int strlen(const char*); means strlen can accept either a char* or const char* without needing a cast.

If you pass a pointer to a non-const variable and the function attempts to modify it (by casting away the const as in void modify(char const *X){ *(char*)X='x'; }) the behavior is ̶i̶m̶p̶l̶e̶m̶e̶n̶t̶a̶t̶i̶o̶n̶-̶d̶e̶f̶i̶n̶e̶d̶ undefined (it is undefined, not implementation defined in newer C versions).

Undefined behavior means you lose all guarantees about the program.

Implementation defined means the code will do something specific (e.g., abort, segfault, do nothing) in a consistent, predictable fashion (given a platform) and that it that won't affect the integrity of the rest of the program.

In older simple-minded compilers, a static-lifetime const-variable would be placed in read-only memory if the platform has read-only memory or in writable memory otherwise. Then you either get a segfault on an attempt to modify it or you won't. That would be implementation-defined behavior.

Newer compilers may additionally attempt to do far-reaching optimization based on the const annotations, so it's hard to tell what effects an attempt at such a modification attempt may have. The fact that such a modification attempt is undefined behavior in modern C allows compilers to make such optimizations (i.e., optimizations which yield hardly predictable results if the rule is broken).

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142