当前位置:C++技术网 > 资讯 > 设置文件指针的函数SetFilePointer的分析

设置文件指针的函数SetFilePointer的分析

更新时间:2015-06-23 16:00:13浏览次数:1+次

    此函数用来设置文件指针的位置。有四个参数,依次是文件句柄、移动的距离低32位(LONG类型)、移动距离高32位,移动的起始位置。具体的含义请查询MSDN了解,在此只讲述让人迷惑不解的地方。
    我对一段数据进行两次的读写,并且是跳跃性的读取,因此就需要继续文件指针定位。在第一次读取时,使用此函数能够正确读出数据,可是第二次读取却是乱码。对地址等都再三检查,读取代码也正确无误,最后只有把焦点放在不太熟悉的SetFilePointer函数上面,然后查阅MSDN和验证,再仔细分析,才最终确定问题就是出在这个函数了。因为是测试,所以没有添加错误检验。虽然我在网上找到代码,运行后确实能够正确,但是引入了LARGE_INTEGER这个数据结构,如果照用,肯定是不会有错的,但是这个结构又会将问题复杂化。所以就进一步查MSDN,仔细分析和使用代码验证,最后只用简单的数据类型同样实现正确的读取,这样才找到问题的核心。经过假设和验证,证明假设是正确的。
    VS中,LONG类型占32位。如果一个文件很大,32位无法定位到表示范围之外的位置。这样设置文件指针,就超出表示范围而出问题。第二个参数是有符号数,有正负数之分。如果偏移量传给第二个参数来移动文件指针,如果正向偏移过大,传递给第二个参数,最高位则作为了符号位,结果将最高位的1解释成负数,从而函数内解释成反向移动文件指针,如果文件指针移动起始地址是FILE_BEGIN,则将文件指针移到文件开始的前面了。因而不是有效的移动,指针则不会移动。如果此时读取数据,则是从原来的位置开始读取,而不是从应该设置的位置读取,所以读取出错。而我第一次读取数据时,因为偏移量小,只用16位即可表示出来,没有超出表示范围,因为文件指针正确移动了。而第二次设置文件指针,因为正数偏移量超出了LONG的正数表示范围,最后就将最高位的1当做符号处理,结果文件指针根本就没有移动。
    以上是问题的描述。既然知道了问题所在,解决方法就不难了。在正向移动文件指针时,偏移量太大,LONG的正数部分范围不够,所以将这个数值传递给LONG,则会进行强制转换,将无符号的值转成有符号的值,最后转成了负数,结果错误。我们可以自己处理,那就是将偏移量进行细分,移动多次,使用相对于当前的位置,可以解决问题。但是,这样效率不高,且存在隐患。好在这个函数已经考虑到这个问题,提供了第三个参数。一般刚接触这个函数的人,一定不理解第三个参数的用法的。所以,重点就在第三个参数的理解上,如果这个参数没问题了,这个函数就可以运用自如了。
    前面已经说过了,因为超出范围,导致出错。如果我们在将变量变长,那么这个问题就不是问题了。函数内部就提供了这个机制。
    在偏移量较小的时候,LONG类型值可以很好的工作,这个时候,第三个参数传入NULL即可,因为此时用不着。但是如果偏移量很大,那么就要借助第三个参数。根据MSDN描述,如果第三个参数不为空,那么函数内部就认为传入的偏移量是很大的,需要更长的位数来表示,因此,内部就将第二个参数作为了无符号数进行处理,而把第三个参数作为有符号数进行处理,第二个参数作为低32位,第三个参数作为高32位,从而拼出64位,这样就轻松解决了这个问题。即使你发现,第三个参数是0,但是却起了很大的作用,也就是,用来放符号位的,这样第二个参数就可以腾出符号位,全部用来表示数值。所以此时就不要把第二个参数当做有符号数看待了,里面的每一位都是有效数值。如果第二个参数表示不下,则进位到第三个参数。所以第二和第三个参数,我们就可以用普通的LONG型的变量就可以了。至于使用LARGE_INTEGER结构体变量的使用,只是减少变量个数而已。
    还有一个问题,这个函数会返回新文件指针的位置。既然移动时就出现不够表示的问题,那么返回值的同样会出现不够表示的问题。因为返回值的长度是32位,即使用64位的变量接受也是没有用的,因为返回之前,数据就不够表示了。当然你不用担心,函数已经考虑到了。函数返回的值,如果是正确执行了,那么这个值是文件指针的低32位,高32位则放到了第三个参数中,这样将两个变量组合起来即可得到正确的64位值。这也是第三个参数为什么是一个指针类型的原因。这也是刚接触此函数比较模糊的地方。对此函数的返回值进行检查,以防移动文件指针错误还继续执行的情况,可能导致很严重的问题。
    当然,微软后来提供了一个SetFilePointerEx函数,将第二个和第三个参数从32位提升为64位来解决问题,返回值则是布尔值,可以很容易判断是否执行成功,第三个参数用来接收新文件指针的值。所以,如果想图方便,就直接使用SetFilePointerEx函数了,而不需要考虑这么多问题。这也是为什么使用SetFilePointerEx比SetFilePointer好的原因。
    现在来看,你清楚了这些问题了吧。