2016-08-26 67 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