3

I am new to C programming (although I have experience with Java). After reading some tutorials, I decided to start solving coding challenges on Coderbyte.

The first challenge I tried was this one:

Challenge

Have the function FirstFactorial(num) take the num parameter being passed and return the factorial of it. For example: if num = 4, then your program should return (4 * 3 * 2 * 1) = 24. For the test cases, the range will be between 1 and 18 and the input will always be an integer.

Sample Test Cases

Input: 4
Output: 24

Input: 8
Output: 40320

My solution:

#include <stdio.h>

void FirstFactorial(int num[]) {

  int i = num -1;

  for(i ; i > 0; i--) {
    num = num * i;
    printf("%d",i);
  }

  printf("\t %d", num);
}

int main(void) {

  // disable stdout buffering
  setvbuf(stdout, NULL, _IONBF, 0);

  // keep this function call here
  FirstFactorial(gets(stdin));
  return 0;

}

Value of the input parameter: 8

Error message:

main.c: In function 'FirstFactorial':
main.c:5:11: warning: initialization makes integer from pointer without a cast [-Wint-conversion]
   int i = num -1;
           ^~~
main.c:8:15: error: invalid operands to binary * (have 'int *' and 'int')
     num = num * i;
               ^
main.c: In function 'main':
main.c:23:18: warning: passing argument 1 of 'FirstFactorial' makes pointer from integer without a cast [-Wint-conversion]
   FirstFactorial(8);
                  ^
main.c:3:6: note: expected 'int *' but argument is of type 'int'
 void FirstFactorial(int num[]) {
      ^~~~~~~~~~~~~~

exit status 1

So there seems to be a few problems, and I have a few questions:

  1. I've never heard of gets(stdin). I looked up gets(), and the glibc documentation says the function returns a char*. How can I pass it to a function that takes an int?

  2. It looks like

    int i = num -1;
    

    is initializing i as 4 and not 7. Why?

  3. The for loop seems to be decrementing i correctly (i = 7, 6, 5, 4, 3, 2, 1). But this statement:

    num = num * i;
    

    is generating an error. What is wrong with it? It looks like a normal multiplication.

Community
  • 1
  • 1
Limon
  • 63
  • 8
  • 9
    Without even reading the rest of the question: First forget about `gets()`, it doesn't exist any more in current versions of C for good reasons. It's impossible to write correct/safe code with `gets()`. –  Apr 17 '18 at 06:54
  • 6
    Never *ever* use `gets`. It's dangerous and prone to buffer overflows, and therefore have been removed from the C specification. Use e.g. [`fgets`](http://en.cppreference.com/w/c/io/fgets) instead. – Some programmer dude Apr 17 '18 at 06:54
  • 8
    Please burn your current book or teacher, it is teaching you completely outdated knowledge. See [Why is the gets function so dangerous that it should not be used?](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-so-dangerous-that-it-should-not-be-used). – Lundin Apr 17 '18 at 06:54
  • 3
    To read integers, instead use `scanf("&d", &var);`. – HolyBlackCat Apr 17 '18 at 06:56
  • 6
    And you ***really*** need to get a beginners book ASAP. Your code makes no sense, it seems you are just *guessing* about how C works, which is a really bad way of doing programming in any language. – Some programmer dude Apr 17 '18 at 06:56
  • 1
    Thanks for the comments. I read about gets and the bufferoverflow problem. The structure was given by coderbytes.com I only did the part inside the FirstFactorial() function. – Limon Apr 17 '18 at 07:02
  • 2
    To give you some perspective on why every one here is so harsh about this. Coming from Java you grew up in a protected world where you get an error/exception everytime you do something wrong(e.g. array out of bounds). C is different, it is unforgiving in mistakes you make, it has no exceptions and it does not check if try to write somewhere where you shouldn't. C expects that you exactly know what you do, it doesn't generally warn you if you do things that will break your programm (look up undefined behavior). So It is really important to get a good knowledge about it (by a book or teacher). – Kami Kaze Apr 17 '18 at 07:10
  • 2
    The code is full of bugs. If you turn on/up compiler warnings the compiler will help you find many of them. – Klas Lindbäck Apr 17 '18 at 07:19
  • 1
    You shouldn't need to use `setvbuf()` — it has its uses, but not in beginner programs. You should end your output messages with a newline. There are other problems too — but using `gets()` is a disaster. If someone is teaching you about `gets()`, they're a decade (and more) out of date in their knowledge. (And when it was a standard function, `gets()` returned a `char *`, not an `int`. It isn't a standard function any more, and you should forget that it ever existed and assume it makes your program crash out of control if you ever have the temerity to use it.) – Jonathan Leffler Apr 17 '18 at 07:33
  • 2
    Yeah - gets() has a habit of not returning at all, (because it has destroyed the return address when it overflowed its buffer:). – Martin James Apr 17 '18 at 07:42
  • @MartinJames But then all is well and good, it has returned, not to main() but to some other address where friendly injected code resides, written by a fellow programmer comrade. – Lundin Apr 17 '18 at 09:29
  • I am not sure what your Java experience is, but if `num` is an array, `int i = num - 1` and `num = num * i` would not compile in Java either. – vgru Apr 17 '18 at 09:45

3 Answers3

6

For the sake of future visitors:

This is a horrible abuse of the language that Coderbytes uses for "convenience". gets(stdin) should never work in the first place: the types don't work out.

What's actually happening is Coderbytes is blindly finding-and-replacing the first instance of gets(stdin) with the literal string you've supplied as input, before sending your code to the compiler. This isn't even a preprocessor macro, it's a blind substitution on the source.

So while you should never do this in reality, on Coderbytes it's a necessary evil: this seems to be the only supported way to put input into your program.

Source


Also, if you want some amusement, try clearing out everything else and putting this into Coderbytes:

int main(){
    printf("%s", "This is a literal string containing gets(stdin) along with other words");
}

You'll see that the substitution happens even inside string literals!

Draconis
  • 3,209
  • 1
  • 19
  • 31
  • This is the correct answer to question #1 – `gets(stdin)` only "works" because of Coderbyte shenanigans. The other answers missed that. However, those answers are still valuable for their responses to questions #2 and #3. – MultiplyByZer0 Mar 22 '19 at 03:27
4

Ignoring that gets is dangerous and has therefore been completely removed from the C language as per Why is the gets function so dangerous that it should not be used?, here are the answers to your questions:

  1. I never used gets(stdin). I cheked it on the C-Library reference. It looks like it would return a character (yeah userinput). Why can I pass it to the function as integer?

Nobody ever used gets(stdin) because it is expecting the parameter to be a pointer to a character buffer where the result is stored, not stdin. Unlike fgets, gets can only read from stdin and is therfore set to stdin by default - you can't change it.

You can't pass it to the function expecting an int[]. Your compiler must give a diagnostic message here, since a char* returned from gets is not compatible with int[]. If your compiler gave no such message, it is broken and should not be used.

The gcc compiler does give a message here, and not the one you have quoted. It smells as if you are running gcc in gnu90 ("crap mode"), which is not recommended for beginners. See the bottom of this answer for how you should be running it.

  1. It looks like int i = num -1; Is initialising i as 4 and not as 7. I don't understand why?

That line is not valid C. num in this case is an array, which gets adjusted to type int* since it's a function parameter. num - 1 therefore gives pointer arithmetic, which is not what you want here. The result is of type int*. You cannot assign an expression with resulting type int* to an int. Again your compiler must give a diagnostic message and it does so correctly:

warning: initialization makes integer from pointer without a cast

If it produces an executable despite the above message, the behavior of that program is undefined, since it is not valid C, and then anything can happen.

  1. But it looks like the statement: num = num * i; is not working.

For the same reason as above, num is declared as an array so you can't perform arithmetic on it in any sensible way.


Overall, you can't program by "take a guess trial & error", programming doesn't work that way. You must actually know what every single line of code does. I would strongly recommend you to turn up the compiler warnings to the highest level and make sure there are no warnings before you run the program:

gcc -std=c11 -pedantic-errors -Wall -Wextra
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thanks for the explanations. It seems like I have a lot to learn now. I now use an int instead of the array and replaced the gets function. It's working now! But I have to read more about these pointers and arrays. – Limon Apr 18 '18 at 10:04
0

As was mentioned, you should not use gets() since it is horribly prone to buffer overflows (which you definitely need to learn about); as other people have mentioned, a much better alternative is fgets. The reaons it is better is because it will only write into the buffer up to the number of bytes which you have specified as the length; if it didn't stop, then it would keep writing past the end of the buffer into other memory, which is bad. This leads to lots of security problems and just plain crashing.

Dealing with other the issues in your code as follows:

In C types are a bit more fluid than what you are used to in Java. A variable is simply a spot in memory; you can interpret that spot in memory as different things, and C lets you cast between types pretty easily, which can bite you if you aren't careful.

Arrays in C are always pointers, or memory addresses, so for int num[], num is a pointer, not an int, it would be like int* num. This is why your compiler is giving you warnings when you try to do arithmetic operations on num and int variables. This means that what you get out of gets, or fgets, is a string, not an int, so passing the raw output of gets into FirstFactorial will give you back garbage. What you need to do is get the integer that the string represents.

What gets returns can be used as an int because it is a char*, which is an address, which is able to be "interpreted" as an integer (since it is really a set of bytes that hold a number that points to a memory address). Since they are all really just bit strings, the compiler can interpret them that way, but will warn you that you probably don't semantically mean what you are telling it to do.

Strings of characters are really just numbers in memory that "represent" the glyph of the character (like what ASCII is: http://ee.hawaii.edu/~tep/EE160/Book/chap4/subsection2.1.1.1.html) What you are doing is like this FirstFactorial('4'), what you want is FirstFactorial(4). The best way to convert that string '4' to 4 is to use strtol (with examples here: https://www.techonthenet.com/c_language/standard_library_functions/stdlib_h/strtol.php), but atoi is slightly easier, as an example that does semantically what your code does but is safer and compiles:

#include <stdio.h>

void FirstFactorial(int num) {

    int i = num -1;

    for(i ; i > 0; i--) {
        num = num * i;
    //        printf("%d",i);
    }

   printf("\t %d", num);
}

#define BUFFER_LENGTH 60
int main(void) {

   char str[BUFFER_LENGTH];

   // disable stdout buffering
   if( fgets (str, BUFFER_LENGTH, stdin)!=NULL ) {
       int num = atoi(str);
       FirstFactorial( num );
   }
   // keep this function call here
   return 0;

} 

Note the #define, this is a preprocessor macro that helps keep the 60 common between the buffer size and the length passed to fgets. This helps if you decide you need to change the size of the buffer; you might change the buffer size (say to 40), but forget to change the length passed to fgets; you would be back with the problem of fgets writing up to 60 bytes into the buffer which is only 40 bytes, which means you could overwrite 20 bytes of other memory, which is, again, bad.

Getting a modern book on C would be very helpful, and a mentor or tutor to help walk you through the differences could be very profitable in saving both time and in avoiding some of the pitfalls that can easily come about and lead to issues in your code. C is a bit harder to just pick up the best practices without some guidance on why things are or aren't done.

Halcyon
  • 1,376
  • 1
  • 15
  • 22
  • 1
    Please don't teach beginners to use `atoi`. Use `strtol` instead, as it does exactly the same thing but is safe. – Lundin Apr 17 '18 at 09:10
  • 2
    "What gets returns can be used as an int because it is a char*, which is an address, which is able to be "interpreted" as an integer" This is incorrect, a char* cannot be implicitly converted to an integer. That's a constraint violation of simple assignment (arguments are copied to parameters as per the rules of assignment) and therefore not valid C. If gcc spits out a binary despite warning for this, all bets of what it does are off. Undefined behavior. "Since they are all really just bit strings, the compiler can interpret them that way" No, not a C compiler. A gnu poop compiler might. – Lundin Apr 17 '18 at 09:22
  • hehe, sometimes I learn more from the comments on my answers when I answer things than in asking questions. Thank you for the feedback! – Halcyon Apr 17 '18 at 09:43
  • Thank you for the references I will check them out. It looks like C is a lot more dificult than Java. I think Ineed to learn everything from scratch and forget about Java. – Limon Apr 18 '18 at 10:05