问题
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: 24Input: 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:
- I've never heard of
gets(stdin)
. I looked up gets(), and the glibc documentation says the function returns achar*
. How can I pass it to a function that takes anint
? It looks like
int i = num -1;
is initializing
i
as 4 and not 7. Why?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.
回答1:
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!
回答2:
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:
- 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.
- 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.
- 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
回答3:
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.
来源:https://stackoverflow.com/questions/49871653/why-does-getsstdin-return-an-integer-and-other-errors