Type mismatch but still works

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

int main() {


int arr[10];

for (int i=0;i<10;i++){

        arr[i]=0;

}




    char *s;
    s = malloc(1024 * sizeof(char));
    scanf("%[^\n]", s);
    s = realloc(s, strlen(s) + 1);

 
char*c=s;
for (c;*c!=NULL;c++)//in this loop arguments if i dereference c it works but if i dont it shows segmentation                                                                      
{
        printf ("%c",*c);
        }

    return 0;
}

I’ve edited your post for readability. When you enter a code block into a forum post, please precede it with a separate line of three backticks and follow it with a separate line of three backticks to make it easier to read.

See this post to find the backtick on your keyboard. The “preformatted text” tool in the editor (</>) will also add backticks around text.

Note: Backticks are not single quotes.

markdown_Forums


If you can explain what problems you're having or what your question is, @BobSmith may be able to help.

thank you so much sir.
i have written the problem as comment .

Okey, this depends upon the difference between the pointer and the contents of the pointer.

Recall that for a pointer p, the value of p is the address where your data is stored (the variable points to the data) and *p is your data (called dereferencing, as you said above). Contrast this with an int i which contains an integer (your data) and the pointer &i that is the address where your data is stored.

Your loop for (c; *c != '\0'; c++) can be translated to words: for values of the variable c starting where it is currently set and continuing until the value of *c is \0, repeat the body of the loop while incrementing c.

[ Note: This post originally used NULL instead of \0. See the discussion below for a clarification. ]

That’s a lot of words, so let’s make it into a todo list of what happens:
On each iteration

  1. Check that *c != '\0' (the null character)
  2. printf the data *c
  3. Increment c

Now, this loop works because strings in C are null terminated. This means that when scanf reads the user input, the string provided by the user is placed into memory with a null character, \0, added to the end. For example, the 17 character string sample user input is stored as the 18 character string or sample user input\0.

The contents of the last character in the string are set to \0, so you need to check in your loop condition for the contents of the character pointer to be \0.

If you instead use the loop for (c; c != NULL; c++), the you are checking if the pointer is NULL. That’s never going to happen. We can modify the loop body to see why:

  char*c = s;
  for (c; *c != '\0'; c++) {
    printf ("%c at %p\n", *c, c);
  }

running this code through repl.it, we can see output such as

s at 0xe2f260
a at 0xe2f261
m at 0xe2f262
p at 0xe2f263
l at 0xe2f264
e at 0xe2f265
  at 0xe2f266
u at 0xe2f267
s at 0xe2f268
e at 0xe2f269
r at 0xe2f26a
  at 0xe2f26b
i at 0xe2f26c
n at 0xe2f26d
p at 0xe2f26e
u at 0xe2f26f
t at 0xe2f270

The string is sitting somewhere in the middle of your computer’s memory. The address of the \0 character at the end of the string will never be NULL itself. In this case, the \0 character will be at 0xe2f271. The \0 character has to be stored somewhere. The test c != NULL must always return false for the \0 character, or any other character in your string, or any random location in your memory!

This means that your loop body will run forever. The segmentation fault occurs when you try to print an invalid character or overrun the available memory. The segmentation fault comes from invalid memory access.

Edit: Updated to clarify NULL vs \0.

2 Likes

Three unrelated notes:

  1. Generally, its considered best practice to avoid scanf. It’s fine in a teaching context, but you can get a lot of security problems if you use scanf. See this link.

  2. I generally recommend looping over the length of the string rather that iterating on a pointer. Both get the job done, but looping over the length of the pointer is more human readable. See repl.it

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
  // Read the user input string
  char *s;
  s = malloc(1024 * sizeof(char));
  scanf("%[^\n]", s);
  s = realloc(s, strlen(s) + 1);

  // Print the string
  for (int i; i < strlen(s); i++) {
    printf ("%c at %p\n", s[i], &s[i]);
  }

  return 0;
}
  1. The change I gave you in 2) avoids some compiler warnings that the first version of your code had. In general, compiler warnings are bad. You want your code to compile with zero warnings. Even if your code seems to run fine with the warnings, the warnings are there to let you know that your code violates standards and best practices or your code relies on features that are planned for deprecation or your code may not in fact be doing what you believe. Trust the compiler! Avoid warnings!
1 Like

Thank you so much sir for such elaborate explanation.
I just have one more doubt
NULL-> a pointer
\0-> a character
i have used *c !=NULL;
and this goes on well, does that mean i can interchangeably use \0 and NULL?

Great question!

As you said, NULL is a pointer, so the comparison *c != NULL is playing fast and loose. This test only works because \0 and NULL are falsey. *c != NULL is really checking if *c is falsey. In general you can’t use NULL and \0 interchangeably.

On repl.it, you can see that the compiler doesn’t like this, as it gives us the warning: main.c:14:14: warning: comparison between pointer and integer ('char' and 'void *')

In this case you can use

  for (c; *c != '\0'; c++) {
    printf ("%c at %p\n", *c, c);
  }

and you will actually get one fewer compiler warning!

Since \0 is falsey, the following code will also work

  for (c; *c; c++) {
    printf ("%c at %p\n", *c, c);
  }

though, it is less human readable!

1 Like

I updated my post to me more correct.

1 Like

Wouldn’t we even take it one step further in making it human readable (and avoid using “magic values”) by declaring this known null-terminator ( \0) as a constant STR_TERM and then checking *c != STR_TERM.

You can, but that’s not super common as far as I know. It’s more common to use the built in C standard string library to avoid this sort of direct access of single characters (unless you really need the granularity, which is the power and danger of C!).