2010-08-10 94 views
3

我在网站上看到这段代码。“Puts()”函数如何在没有参数的情况下工作?

main(i) 
{ 
    gets(&i); 
    puts(); 
} 

此代码编译并运行良好!

它得到一个字符串作为来自用户并打印输入!!!!

但是,我的问题是,怎么样?

(注意:puts()函数不包含任何参数!)

+8

@R:什么时候该愚蠢的“你为什么问这个问题?”评论结束?他看到了一些对他来说毫无意义的事情,所以他提出了一个清楚而有力地提出问题的问题。为什么那么糟糕?你为什么认为每个人都已经知道什么是或不是未定义的行为?或者,大家已经知道,编译器可能无法捕捉到未定义的行为。一个新的开发人员不会知道这一点,新开发人员是我们在这里提供帮助的人员之一。 – 2010-08-10 14:01:51

+0

@Nicholas骑士:我认为,这些演习是从立场来看,他们展示一些东西可以如何通过看似意外的工作有趣。类似这样的缺陷的程序很可能会通过一些开发组的测试,并被认为是“我们信任的好代码”,但由于许多原因它很容易被破解。了解这种类型的东西对于找出为什么有些东西不起作用或发现一些安全缺陷也很有用。 – nategoose 2010-08-10 16:11:50

回答

5

的C旧版本有隐含类型变量和函数,这个代码利用了这一点,一些其他的东西。实际返回价值也很松懈。

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. 
} 
3

那是因为你刚刚叫gets()用正确的参数调用puts()发现堆栈不变。在具有多个寄存器的CPU上,除非gets()不使用包含第一个参数的寄存器,否则这可能会中断。编译启用优化,这可能就足够了。

如果把两者之间的任何函数调用,它会破坏了。

用相同数量代码的清洁方法是:

puts(gets(&i)); 
+5

有没有干净的方式来使用'gets()'。 – JeremyP 2010-08-10 16:29:39

0

gets(&i)功能实际上得到的字符串。 puts()在您声明两个语句的顺序中没有影响。

0

通过它不一定每台机器和执行工作组的魔法。 puts();仅仅意味着你没有参数传递,但有些东西在堆栈上,它是指向字符串的指针(确实是这样)。 puts需要它(它不知道你没有在堆栈上推动任何东西,它只是“相信”你做到了)并且它工作。由于调用者需要清理堆栈,所以一切都很顺利(如果它是一个被调用者任务,就会出现问题)。它运作的事实是一个“机会”(可能会发生,但你不能太信任的东西);它编译的事实是由标准或编译器确定的,警告但不停止编译(可能会添加选项以严格遵守特定标准,然后代码可能无法编译)

2

当您说“编译和运行正常“,你的意思是(a)你忽略了编译器警告,(b)代码出现”运行正常“。您的编译器应该生成多个警告,例如

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 

另外,如果你尝试这种在一个以上的平台,你会发现它并不总是“运行良好” - 它很可能打印垃圾和/或崩溃。

+0

控制允许在没有'return'语句的情况下到达'main'的末尾,编译器不应该抱怨。 – dreamlax 2010-08-10 14:01:43

+0

@dreamlax:我可能是错的,但我认为这是对C的有效警告(C++可能不同?)。 – 2010-08-10 14:04:53

+1

@myself:对于GCC,使用'-std = c99'可以取消警告。 Clang不会发出这个警告(对于'main',它对其他人来说是这样的)。 – dreamlax 2010-08-10 14:05:04