2016-08-26 62 views
3

作教育用途我嘗試訪問FILE結構拉斯特:C結構的錯誤映射生鏽

unsafe { 
    let passwd = libc::fopen("/etc/passwd".to_ptr(), &('r' as libc::c_char)); 
    let fp = &mut *(passwd as *mut MY_FILE); 
    println!("flags={}, file={}", fp._flags, fp._file); 
} 

的運行上stdio.h中的BindGen獲得MY_FILE結構I(我在OS X) :

bindgen /usr/include/stdio.h 

不知怎的_flags始終是8在(讀取模式下4)寫模式打開的文件,所以這個標誌似乎關閉(我用C代碼進行測試,以驗證它確實不是4或8個)。但文件指針似乎是正確的。什麼會造成這種情況?我是否從錯誤的頭文件中提取綁定?有什麼我需要添加到#[repr(C,)]屬性?

Here是包含該結構的完整代碼。

這是從an earlier question

+0

您發佈的代碼不會編譯(我認爲fp和passwd混淆了)。另外,你傳遞給C函數的字符串不是NUL終止的,所以你的代碼有完全未定義的行爲。 –

+0

哦,是的,我忘了添加一個NUL終止的文件,我已經更新了上面的要點 – hansaplast

+0

不用擔心每次都漏出字符串?無論如何,我認爲第二個論點有同樣的問題。 –

回答

2

首先跟進的問題,你的ToPtr實施邀請不健全的代碼。這裏轉載:

// code in italics is wrong 
impl ToPtr for str { 
    fn to_ptr(&self) -> *const i8 { 
     CString::new(self).unwrap().as_ptr() 
    } 
} 

這種分配一個新CString,並返回一個指向它的內容,但CStringto_ptr收益下降,所以這是一個懸擺指針。 該指針的任何解引用都是未定義的行爲。documentation對此有一個很大的警告,但它仍然是一個非常常見的錯誤。

從字符串文字生成*const c_char的一個正確方法是b"string here\0".as_ptr() as *const c_char。該字符串以null結尾,並且沒有懸掛指針,因爲字符串文字在整個程序中都存在。如果你有一個非常要轉換的字符串,你必須保持CString活着正在使用它時,就像這樣:

let s = "foo"; 
let cs = CString::new(s).unwrap(); // don't call .as_ptr(), so the CString stays alive 
unsafe { some_c_function(cs.as_ptr()); } 
// CString is dropped here, after we're done with it 

旁白:編輯「建議」(我新堆棧溢出,但它似乎更有禮貌發表評論,而不是試圖重寫我的答案),上面的代碼可以這樣寫:

let s = "foo"; 
unsafe { 
    // due to temporary drop rules, the CString will be dropped at the end of the statement (the `;`) 
    some_c_function(CString::new(s).unwrap().as_ptr()); 
} 

儘管這在技術上是正確的(最好的一種正確的),涉及的「臨時放置規則」很微妙 - 這工作,因爲as_ptr需要到CString引用(實際上是一個& CStr的,因爲編譯器改變方法鏈的CString ::新(S).unwrap()DEREF()as_ptr()。)而不是消耗它,並且因爲我們只有一個C函數可以調用。編寫不安全代碼時,我不喜歡依賴任何細微或不明顯的東西。


有了這樣的方式,我fixed that unsoundness在你的代碼(您的通話都使用字符串文字,所以我只是用我上面的第一個策略)。我在OSX上得到這個輸出:

0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0 
0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 0 
0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 0 
0, 0, 4, 3, 0, 0, 0, 0, 0, 0, 0 

所以,這符合你的結果吧?我也寫了下面的C程序:

#include <stdio.h> 
#include <unistd.h> 

int main() { 
    struct __sFILE *fp1 = fdopen(STDIN_FILENO, "r"); 
    struct __sFILE *fp2 = fdopen(STDOUT_FILENO, "w"); 
    struct __sFILE *fp3 = fdopen(STDERR_FILENO, "w"); 
    struct __sFILE *passwd = fopen("/etc/passwd", "r"); 

    printf("%i %i %i %i\n", fp1->_flags, fp2->_flags, fp3->_flags, passwd->_flags); 
} 

,並得到了輸出:

4 8 8 4 

這似乎證實了這項防鏽效果。有在/usr/include/stdio.h頂部寫着評論:

/* 
* The following always hold: 
* 
* if (_flags&(__SLBF|__SWR)) == (__SLBF|__SWR), 
*  _lbfsize is -_bf._size, else _lbfsize is 0 
* if _flags&__SRD, _w is 0 
* if _flags&__SWR, _r is 0 
*/ 

仰起臉這些常量:

#define __SLBF 0x0001  /* line buffered */ 
#define __SRD 0x0004  /* OK to read */ 
#define __SWR 0x0008  /* OK to write */ 

這似乎是我們得到的輸出相匹配:4在讀模式打開的文件,8寫。那麼這裏有什麼問題?

+0

感謝關於搖晃指針的解釋,這些事情非常重要。它在文檔中,我沒有閱讀。然後:我的C代碼來自高級的unix編程書。在我發現大多數FILE結構體變量只是在流讀/寫時被填充之後,我在書中發現了這樣一句話:「請注意,我們在打印緩衝狀態之前在每個流上執行I/O,因爲第一個I/O操作通常會導致緩衝區被分配給一個流「(同樣,RTFM給我..)工作要點:https://gist.github.com/philippkeller/c683dda9d89ca0f8cb6830b6dc52910b – hansaplast