2010-08-10 95 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