-3

I recently answered a question here in SO where the problem was to input a few sentences of code and print it. My code is this:

#include<stdio.h>  

int main() {
    clrscr();
    int line, i;

    char (*string)[100];
    printf("How many line?\n");
    scanf("%d",&line);
    getchar();

    for(i=0;i<line;i++) {
        gets(string[i]);
    }

    printf("You entered:\n");
    for(i=0;i<line;i++) {
        puts(string[i]);
    }

    return 0;
}

As you see, I haven't allocated any memory to individual strings i.e, s[0],s[1] ,....... but surprisingly my compiler didn't give me any warnings or errors for it and it works perfectly.

So I posted this code and frankly got quite a few of downvotes for it (I know I deserved it). Can you please explain why this code works and not give a segmentation error?

My output is as shown:

Output

Spikatrix
  • 20,225
  • 7
  • 37
  • 83
Sourav Kanta
  • 2,727
  • 1
  • 18
  • 29
  • 1
    It is just Undefined Behavior. A segfault or a crash isn't necessary. And don't use `gets`. You know why. Also, if you are using `clrscr()`, include `conio.h` – Spikatrix Jun 21 '15 at 06:28
  • 6
    Both C and C++ have removed `gets`. I highly recommend not using it. You might like to know that `string` is a pointer to an array of 100 characters, not an array of 100 `char *`s. – chris Jun 21 '15 at 06:28
  • 6
    Pure bad luck that it didn't crash resoundingly. See [Why `gets()` is too dangerous to use](http://stackoverflow.com/questions/1694036/why-is-the-gets-function-dangerous-why-should-it-not-be-used). – Jonathan Leffler Jun 21 '15 at 06:29
  • 3
    Looking at the title bar, is "TC2" Turbo C 2? No wonder there's not much memory protection. – lmz Jun 21 '15 at 06:31
  • 2
    It's not crashing because of pure dumb luck. It crashes for me, FWIW, under `clang`. `string` is an uninitialized array of 100 pointers, and so you're probably just overwriting random chunks of memory with `gets`. – nneonneo Jun 21 '15 at 06:36
  • 3
    The compiled code/data does not raise segfaults - the OS does it. It doesn't matter whether the compiler is TC, clang or gcc, if the illegal accesses don't cross over page boundaries and attempt to r/w memory that was not allocated by the OS, then there will be no segfault interrupt generated by the hardware. The OS doesn't know or care about incorrect, or missing, use of the C-internal sub-allocator as long as no pages are accesssed without the correct permissions. – Martin James Jun 21 '15 at 06:37
  • Thanks guys. Guess it was bad luck for me. – Sourav Kanta Jun 21 '15 at 06:39
  • 1
    @nneonneo No, `string` is an uninitialized pointer to an array of 100 `char`s, as @chris said. – T.C. Jun 21 '15 at 06:43
  • Ah, yes. I see that syntax so infrequently that I simply misread it. This makes the lack of crash more plausible since it requires only one pointer to be accidentally valid. – nneonneo Jun 21 '15 at 06:47
  • @Sourav Kanta: In your code you don't even have a chance to "allocate memory for individual strings s[0],s[1]...". You declared your `string` as `char (*string)[100]`, which is a pointer to a `char[100]` array. The only way to allocate memory in this case is for all strings at once, as one block. If you wanted to have a chance to manage each string memory individually and independently, you'd have to declare it as `char *string[100]`, which is completely different. Anyway, your code exhibits undefined behavior. It only "works" because you got lucky with the initial garbage value of `string`. – AnT stands with Russia Jun 21 '15 at 07:07

1 Answers1

3
char (*string)[100];

OK, string represents a pointer to 100 char, but it's not initialized. Therefore, it represents an arbitrary address. (Even worse, it doesn't necessarily even represent the same arbitrary address when accessed on subsequent occasions.)

gets(string[i]);

Hmm, this reads data from standard input to a block of memory starting at the random address, plus i*100, until a NUL byte is read.

Not very robust. Not guaranteed to produce an error, either. If you didn't get one, the random address must have been mapped to writable bytes.

As you see, I haven't allocated any memory to individual strings i.e, s[0],s[1] but surprisingly my compiler didn't give me any warnings or errors for it and it works perfectly.

Time to reduce your expectations, or increase the diagnostic level of the compiler. Try -Wall -Wextra, if it takes options in that style. It should be warning you that string is used without being initialized first. A warning that gets has been deprecated would also be a good idea, although my compiler doesn't mention it.

C and C++ allow you to manage memory yourself. You are responsible for validating pointers. In C, this is a major source of problems.

In C++, the solution is to use alternatives to pointers, such as references (which cannot be uninitialized) and smart pointers (which manage memory for you). Although the question is tagged C++ and compiles as a C++ program, it's not written in a style that would be called C++.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Thanks for the through explanation @potatoswatter. Pls help me understand one last thing. As far as I know * has the lowest precedence.So I thought I would declare a char * and have a array of size 100 of it. Thats why i wrote char (*string) [100] . but you said it is pointer to a string of 100 characters. Pls explain it to me even if it seems a very silly question. – Sourav Kanta Jun 21 '15 at 07:45
  • @SouravKanta: You placed the `*string` inside parentheses, so that gets precedence. Look inside the brackets first: "string is a pointer", then look right "to an array of 100" then look left "chars". Removing the parentheses would alter the meaning. If you are in a country that drives on the left, it is like crossing the road, "look right then left". – cdarke Jun 21 '15 at 10:01