1

I was studying "C complete reference" by Herbert Schildt and got stuck on the "const" explanation due by the pointer * he used at the same time with the const explanation. here is the code he used:

#include <stdio.h>

void dash(const char *str);

int main()
{
    dash("this is a test");
    return 0;
}

void dash(const char *str)
{
    while (*str)
    {
        if (*str == ' ')
        {
            printf("%c", '-');
        }
        else
        {
            printf("%c", *str);
        }
        str++;
    }
}

I've tried to search about the pointer * and got some answers about adresses but why did he use it in this example? His book didn't explain this and i haven't found other examples with this kinda use of pointer *. Other question is, why is the loop "while (*str)" correct if it has no condition?

Barmar
  • 741,623
  • 53
  • 500
  • 612
sZru
  • 19
  • 3
  • A condition is nothing more than an expression. Any expression is a valid condition. 0 is false and everything else is true. – klutt Jun 24 '20 at 19:44
  • As @klutt said, **'0' or anything that can be evaluated to '0' is false, everything else is true.** So, `while(*str)` is true, until `str++` reaches to the `NULL` value of the string. – Shubham Jun 25 '20 at 02:09

6 Answers6

4

const char *str in a parameter declaration indicates that the function will not try to modify the values that the str pointer points to. This means that you can call the function with a constant string. If you don't have const in the declaration, it means that the function might modify the string, so you can only call it with writable strings.

As an example, a function like strcpy() declares has const on the second parameter (the source string), but not on the first parameter (the destination). It can (and usually does) modify the destination, but not the source.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • `If you don't have const in the declaration, it means that the function might modify the string, so you can only call it with writable strings.` you can pass any string but if you try to modify it and the string not writable it is an UB. Not having the const (and sometimes restrict) may prevent some code optimisations. – 0___________ Jun 24 '20 at 22:21
  • @P__J__ I guess I was thinking of C++ there, it's more restrictive about const correctness. – Barmar Jun 24 '20 at 22:26
3

It's a way of promising that the content the pointer is pointing at will not be altered. It's also a way of suppressing warnings without explicit casts.

Consider this:

void dash(char *str) // Removed const
{
    // Code
}

int main() {
    const char p[] = "this is a test";
    dash(p);
}

Now the compiler will emit this:

k.c: In function ‘main’:
k.c:23:10: warning: passing argument 1 of ‘dash’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
   23 |     dash(p);
      |          ^
k.c:4:17: note: expected ‘char *’ but argument is of type ‘const char *’
    4 | void dash(char *str)
      |           ~~~~~~^~~

Since you're not writing to it, this warning is nothing to worry about. But it's good practice to avoid warnings. In this case, we have two alternatives. Either the function may modify the string or it may not. If there's no way it will modify it, then there's no reason to explain to the compiler and the reader that this indeed is the case.

Sidenote. String literals, like "this is a test" has undefined behavior if you modify them, so the program might crash (or not). However, their type is is of type (char*) with no const. The reason is backwards compability. In C++, their type is const char*

Note that the const is a promise by convention, not by the compiler. This code will modify the original string and also compile without warnings:

#include <stdio.h>

void foo(const char *str)
{
    // Casting comes with great responsibility
    // You're just saying to the compiler
    // "Trust me and shut up"
    char *ptr = (char*) str;
    ptr[2]='A';
    ptr[3]='T';
}

int main()
{
    const char p[] = "this is a test";
    foo(p);
    puts(p);
}

output:

$ ./a.out 
thAT is a test

As I said, the above will compile without warning. If you remove the cast, you'll get this:

k.c:5:17: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
    5 |     char *ptr = str;
      |                 ^~~

Do note that since p is declared as const this is undefined behavior. However, you instead write main like this:

int main()
{
    char p[] = "this is a test";
    foo(p);
    puts(p);
}

then, the program is completely valid. And even though you pass a writable string to the function foo, you'd expect it to not change, since foo takes a constant pointer as argument. But as you can see, such things can be bypassed.

Be very careful with void pointers

Note that this is perfectly valid for ANY type T:

T x;
T *p;
p = (void*) &x;

This is because you can safely cast a pointer to void and back. However, this is NOT valid in the general case:

T x;
Q *p;
p = (void*) &x;

However, because of the cast, you will not get a warning. But this code invokes undefined behavior.

Moral lesson

Casting is NOT the goto solution for warnings. Instead, you should REALLY carefully consider if your cast match your intentions. If you're intentions here is to just get rid of the warning, the right solution is to remove the const for the parameter. If you're intentions with adding the cast is "I know that this function promises to not modify the argument, but I have good reasons for both promising that and then instantly break that promise" then a cast is correct.

Real world example

Just to give a real world example of how it can go wrong. I looked in this question where I saw this:

void * func_return();
void (*break_ptr)(void) = (void *)func_return;

I told OP that the cast is wrong. I got the response that without a cast, the compiler complained. Well, it complained because the pointer is WRONG. The function prototype declares a function taking an unspecified number of arguments and returning a void pointer. The function pointer is a pointer to a function taking NO arguments returning nothing. So in this case, the proper pointer declaration and initialization would be this:

void * func_return();
void *(*break_ptr)() = func_return;

But this would probably be better:

void * func_return(void);
void *(*break_ptr)(void) = func_return;

Note that since a pointer of any type can be safely cast to void* and back. But in this case OP was not casting it back, but to another type. If OP had done it correctly, the cast would just be clutter, but in this case it did hide the REAL error.

klutt
  • 30,332
  • 17
  • 55
  • 95
3

Many people are confused when start learning C

const char *ptr

It is a pointer which is referencing the const char. The pointer can be modified. But is you try to write to the referenced object the compiler will complain: https://godbolt.org/z/d9znF-

Example:

const char c;
const char *ptr = &c;

*ptr = 'p';  // -- illegal - the compiler will complain
ptr++;       // -- legal 

to declare the constant pointer to the not constant object:

char * const ptr;

now ptr cannot be changed but the referenced object can: https://godbolt.org/z/h7WWex

char c;
char * const ptr = &c;

*ptr = 'p';  // -- legal
ptr++;       // -- illegal - the compiler will complain

to declare const pointer to const object

 const char * const ptr;

now the pointer and the referenced object cannot be modified: https://godbolt.org/z/x2xBcZ

const char c;
const char * const ptr = &c;

*ptr = 'p';  // -- illegal - the compiler will complain
ptr++;       // -- illegal - the compiler will complain
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
0___________
  • 60,014
  • 4
  • 34
  • 74
  • `const char *ptr = &c; ... ptr++;` Pointer arithmetic on a pointer to a variable? Is it legal? – David Ranieri Jun 24 '20 at 22:05
  • @DavidRanieri Yes. Can form pointer "one passed" yet don't de-reference it. – chux - Reinstate Monica Jun 24 '20 at 22:07
  • @DavidRanieri 1. It is legal 2. It is used only for the illustrational purposes – 0___________ Jun 24 '20 at 22:17
  • @chux-ReinstateMonica good to know, but as far as I understand, in the "one past" rule: _If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow_ , talks about array objects, in the snippet `c` is not an array. – David Ranieri Jun 24 '20 at 22:18
  • 1
    @P__J__ _It is used only for the illustrational purposes_ yes, I was not nitpicking, just asking – David Ranieri Jun 24 '20 at 22:19
  • 1
    @DavidRanieri The prior paragraph has "For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type." C17dr § 6.5.6 7 – chux - Reinstate Monica Jun 24 '20 at 22:23
  • @chux-ReinstateMonica BTW IMO if pointer is not use or dereferenced then what is purpose of this limitation.? – 0___________ Jun 24 '20 at 22:25
  • even if it is used like `ptr += 5 ; .... if(ptr == (&x + 5)) ....` what is the danger here? Pointer is used – 0___________ Jun 24 '20 at 22:28
  • I could see calling `foo(1, &c)` and that function needs to form a valid _one past_ pointer to compare. – chux - Reinstate Monica Jun 24 '20 at 22:29
  • Danger of `ptr += N` and `&x + N` is pointer math overflow with an unbounded `N`. C allows _one past_. – chux - Reinstate Monica Jun 24 '20 at 22:31
  • @chux-ReinstateMonica yes. In this case yes. – 0___________ Jun 24 '20 at 22:32
  • 1
    If pointer space is `[0...P_MAX]`, then `char c;` cannot have the address of `P_MAX` due to _one past_ rule - that last memory byte is lost to C usage. If `ptr += N` was allowed where `N > 1` and `ptr + N > ptr` needs to remain true, the usable space becomes less. C choose to stop that loss at 1. – chux - Reinstate Monica Jun 24 '20 at 22:36
2

In c we can manipulate an array like a pointer with the right pointer arithmatic like he used and we can manipulate it like an array!

const char *str

is a pointer to const char OR an array of const char data types!

In a function, all parameters are passed by value (arrays are no exception). When you pass an array in a function it "decays into a pointer". And when you compare an array to something else, again it "decays into a pointer"

so we can write the while loop again in different way:

void dash(const char *str)
{
    int i = 0;
    while (str[i])
    {
        if (str[i] == ' ')
        {
            printf("%c", '-');
        }
        else
        {
            printf("%c", str[i]);
        }
        ++i;
    }
}

Now, the first syntax (with the pointer deref operator * is more effecient than array syntax).

in general array name or the address of the first array element (of any type), can decays to a pointer of the same data type!

In his implementation, he behaves the str as a const char pointer, in the while loop he is derefrence the pointer (like str[i], with the brackets) and in the last line (str++) he is moving the pointer to points to the next char element (which usualy knwon as pointer arithmetics).

Adam
  • 2,820
  • 1
  • 13
  • 33
1

In this case, read the definition from right to left:

const char *str // str is a pointer to a const char

The address of str can change while the char it points to cannot.

To answer you other question, while (*str) will continue to interate until *str == '\0'. '\0' is used to mark the end of a string in C.

What the program does, if you're unsure, is print it, replacing ' ' with '-'. In your example, "this-is-a-test" would be printed. Note: the string "this is a test" is not modified.

Fiddling Bits
  • 8,712
  • 3
  • 28
  • 46
1

The * is related to pointers but it has two uses.

In the declaration, * is used to declare the pointer type, as in:

const char *str;

Where str is a pointer to a const char (or multiple const char stored in sequence, C doesn't care about the difference).

In an expression, * is used to dereference a pointer, get the value it points to. As in:

printf("%c", *str);

Where *str is that const char itself that the pointer str is pointing to.

Related to pointers, there's also & that does the other way around. It gets the pointer of any value you have stored in memory.

The importance of const here is not related to pointers, it's related to the fact you're passing a string literal to dash(). Unlike strings that are stored in the heap or the stack, string literals cannot be modified and should be treated as const for their immutability.

Havenard
  • 27,022
  • 5
  • 36
  • 62