I came across this piece of code on a website.
main(i)
{
gets(&i);
puts();
开发者_Python百科}
This code compiles and runs fine!
It gets a string as input from the user and prints it!!!!
But, my question is, how?
(note that puts()
function does not contain any arguments!)
Old versions of C had implicit types for variables and functions, and this code makes use of that and some other stuff. It also was very lax about actually returning values.
main(i) // i is implicitly an integer (the default type for old C), and normally named argc
// int main(int i) or void main(int i)
{ // The stack (which lives in high memory but grows downward) has any arguments and
// probably the environmental variables and maybe even other (possibly blank/filler)
// stuff on it in addition to the return address for whatever called main and possibly
// the argument i, but at this point that could either be on the stack just under the
// return address or in a register, depending on the ABI (application binary interface)
// extern int gets(int) or extern void gets(int)
// and sizeof(int) is probably sizeof(char *)
gets(&i); // By taking the address of i even if it wasn't on the stack it will be pushed to
// it so that it will have an address (some processors have addressable registers
// but they are rarely used by C for many reasons that I won't go into).
// The address of i is either also pushed onto the stack or put into a register
// that the ABI says should be used for the first argument of a function, and
// and then a call is made to gets (push next address to stack; jump to gets)
// The function gets does what it does, but according to the ABI there are
// some registers that it can do whatever it wants to and some that it must
// make sure are the same as they were before it was called and possibly one
// or more where it is supposed to store a return value.
// If the address of i was passed to it on the stack then it probably would be
// restricted from changing that, but if it was passed in a register it may
// have just been luckily left unchanged.
// Another possiblity is that since gets returns the string address it was
// passed is that it returns that in the same location as the first argument
// to functions is passed.
puts(); // Since, like gets, puts takes one pointer argument it will be passed this
// this argument in the same way as gets was passed it's argument. Since we
// were somehow lucky enough for gets to not overwrite the argument that we
// passed to it and since the C compiler doesn't think it has anything new to
// pass to puts it doesn't change any registers' values or do too much to the
// stack. This leaves us in the situation where puts is called with the stack
// and registers set up in the same way as they would be if it were passed the
// address of i, just the same as gets.
// The gets call with the stack variable's address (so an address high on the stack)
// could have left main's return address intact, but also could have overwritten it
// with garbage. Garbage as main's return address would likely result in a jump to
// a random location (probably not part of your program) and cause the OS to kill the
// program (possibly with an unhandled SIGSEGV) which may have looked to you like a
// normal exit. Since puts appended a '\n' to the string it wrote and stdout is
// line buffered by default it would have been flushed before returning from puts
// even if the program did not terminate properly.
}
That's because you just called gets()
with the correct parameter and the call to puts()
finds the stack unchanged. On CPUs with many registers this will probably break unless gets()
doesn't use the register which contains the first argument. Compile with optimization enabled, that might be enough.
If you put any function call between the two, it will break too.
A clean way with the same amount of code would be:
puts(gets(&i));
When you say "compiles and runs fine" what you really mean is (a) that you are ignoring compiler warnings and (b) the code appears to "run fine". Your compiler should be generating several warnings, e.g.
ub.c:2: warning: return type defaults to ‘int’
ub.c: In function ‘main’:
ub.c:3: warning: implicit declaration of function ‘gets’
ub.c:4: warning: implicit declaration of function ‘puts’
ub.c:5: warning: control reaches end of non-void function
Also if you try this on more than one platform you'll find that it will not always "run fine" - it may well print garbage and/or crash.
Your gets(&i)
function is actually getting the string. puts()
has no effect in the order you declared both statements.
By stack magic that it is not surely working on every machine and implementation. The puts();
simply means you pass no argument, but something is on the stack, and it is the pointer to the string (by chance indeed); puts
takes it (it does not know that you pushed nothing on stack, it simply "believes" you did) and does it work. Since it is up to the caller to clean the stack, everything goes fine (if it would have been a callee task, there would be problems). The fact that it works is a "chance", (likely to occur, but something you can't trust too much); the fact that it compiles, is determined by standard or compiler that warn but do not stop compilation (likely you can add option to respect strictly a specific standard, and then the code could fail to be compiled)
精彩评论