老江湖碰到的新问题
// test.cpp : Defines the entry point for the console application.//
#include "stdafx.h"
/*
// ================== fscanf() ==================
// test.txt : 1 2 3 4 5 6 7 8 9 0 1 2
int main( int argc , char* argv[ ] )
{
FILE *f ;
char buf[ 11] ;
int i ;
if( ( f = fopen( "test.txt" , "r" ) ) == NULL )
{
printf( "Can't Open The File" ) ;
return 0 ;
}
for( i = 0 ; i < 10 ; i++ )
{
//fscanf( f , "%x" , &buf[ i ] ) ; // A
fscanf( f , "%x" , &buf[ 9-i ] ) ; // B
}
fclose( f ) ;
printf( "\nEnd : " ) ;
for( i = 0 ; i < 10 ; i++ )
{
printf( "%02x " , buf[ i ] ) ;
}
printf( "\n" ) ;
return 0;
}*/
/*
这段代码完成的工作就是将文件中的十个数字,从9-0反序放到数组buf中,但是,在实际执行过程中,有下面两个问题
1. 会产生溢出错误....,A,B均有
2. 在B方式下,输出结果全0
这个问题实在是有点让人莫名其妙的
给大家一个分析这个问题的办法,在//B方案下面添加如下代码:
printf( "\nNO.%d : " , i ) ;
for( int j = 0 ; j < 10 ; j++ )
{
printf( "%02x " , buf[ j ] ) ;
}
分析:
1. fcanf("%x")做为一个(四字节)整型X读入,然后强制&0xff( char),然后,按照intel数据顺序,低位在前,高位在后,写四个字节,在A,B方式下,问题1均是由于对buf[9]的赋值产生的边界溢出造成的,buf的缓冲区长度最少应为13.......OMG
2 .在B方式下,由于一次对四个字节赋值,从数组尾向前的运动方式,使的数组从后向前逐个赋0!!
解决办法
1 int buf[10 ];
2 char a;
fscanf(f,"%x",&a);
buf[9-i]=a&0xff;
写这个例子只是想告诉大家,其实很多时候,代码本身是没错的,错在系统,大家要学代码设计,一定要记住,任何代码都是在一定的环境下实现的,要想写好代码,一定要对代码的应用环境有充分的认识。
*/
/*
// ================== 文件操作 ==================
unsigned char pool[ 1 << 20 ] ;
unsigned int buf[ 1 << 20 ] ;
int main( int argc , char* argv[ ] )
{
FILE *f ;
int i , j , k ;
if( ( f = fopen( "test.txt" , "r" ) ) == NULL )
{
printf( "Can't Open The File" ) ;
return 0 ;
}
i=fread(pool,1,1<<20,f); //A
fread(buf,1,1,f); //B
i = 0 ;
while( !feof( f ) )
{
fscanf( f , "%c" , &pool[ i++ ] ) ;
}
fclose( f ) ; // C
return 0 ;
} */
/*
A 此时要注意的是,由于打开方式为"r"而不是"rb",则返回的值i不为读入数据的真实长度.
B 此代码本意为读入一个字节,存入buf[0]中,但是,由于buf为整型缓冲区,而实际上,fread会读入四个字符来补满buf[0],由此产生两个问题:1,读入数据不准确;2,由于文件指针移动了四而不是一,则下一个数据实际读入的是文件的第五个字节.
C 此时要注意的是,最后i的值会比实际读入数据多一个,正确的是,在最后补一条指令:i--;
*/
/*
// ================== 边界 ==================
int main( int argc , char* argv[ ] )
{
char m_ca ;
unsigned char m_ucb ;
unsigned int i , j ;
char m_cBuf[ 257 ] ;
for( m_ca = 0 ; m_ca < 256 ; m_ca++ )
{
m_cBuf[ m_ca ] = m_ca ; // A
}
for( m_ucb = 0 ; m_ucb < 256 ; m_ucb++ )
{
m_cBuf[ m_ucb ] = m_ucb ; // B
}
i = 0 ;
i-- ;
for( j = 0 ; j < i ; j++ ) // C
{
}
}*/
/*
A 有符号的字符型值的范围是 -127-128 ,这段代码会让你很爽的把内存改的乱七八糟,还是个死循环。。。
B 数组初始化只有一种结果:死循环 无符号字符型的表达范围为 0- 255,永远不可能达到 256
C 上述循环会运行2的32次方减一次 !!!
printf( "\n %u \n" , i ) ;
*/
/*
// ================== 编译顺序 ==================
int main( int argc , char* argv[ ] )
{
int i , j ;
i = 1 ;
j = 2 ;
printf( "\ni = 1 , j = 2 , i += ( 4 + j ) = %d , i += 4 + j = %d \n" , (i+=(4+j)) , (i+=4+j) ) ;
return 0 ;
}*/
/*
本例测试 += 操作时,右面的括号有没有用
(实际上,+=的右边只算一个操作数,也就是右边表达式的值。两个+=的计算内容是一样的,都是 i += ( 4 + j ) )
由于本条语句的编译顺序为从右向左,结果为:
Debug :
i = 1 , j = 2 , i += ( 4 + j ) = 13 , i += 4 + j = 7
Release :
i = 1 , j = 2 , i += ( 4 + j ) = 13 , i += 4 + j = 13
都不是原代码的预期结果!!!!
看来采用这种高手编程技术可能会产生算法的二义性.
编写代码的基本要求是安全可靠,既然可能产生问题,我的建议是 : 不采用这种技术
*/
/*
// ================== 计算精度 ==================
int main( int argc , char* argv[ ] )
{
// float(浮点型)的精度非常低,以下程序的最后结果,一般不会是 1
float m_fa , m_fb ;
m_fa = 1 / 12 ;
m_fb = m_fa * 12 ;
printf( "\n\n Test Float : %12.10f \n" , m_fb);
// 此时将数据类型改为double(双精度),会有比较满意的结果
#define MAX_DOUBLE_NUMBER (12345678901234567890.0 )
double m_dfa , m_dfb ;
m_dfa = 1 / MAX_DOUBLE_NUMBER ;
m_dfb = m_dfa * MAX_DOUBLE_NUMBER ;
printf( "\n\n Test Double : %12.10f \n" , m_dfb);
// 结果依然是1.
// 要注意的是,在本例中,double能做的除法长度为10的20次方幂.再长就要采用大数算法了。
return 0 ;
}
*/
/*
// ================== 边界对齐 ==================
int main( int argc , char* argv[ ] )
{
//#pragma pack ( 1 )
struct Type_A
{
int a ;
char b ;
short c ;
} m_stTesta ;
struct Type_B
{
char b ;
int a ;
short c ;
} m_stTestb ;
//#pragma pack ( )
printf("\n Struct A : %d Struct B : %d \n" , sizeof( m_stTesta ) , sizeof( m_stTestb ) ) ;
return 0 ;
}
*/
/* 编译器为了提高代码效率,或者保证代码正确运行,会对数据的起点进行相应的处理
例如:
有些CPU的数据起点必须为偶数,若为奇数则出错;
有些CPU只能从偶数起点读数据,若一个整型起点为偶数,则可一次读入,若起点为奇数,则要两次才能读入
一般情况下,数据起点为2的n次幂,表现即为地址码的某几位低地址为0
VC编译器在默认设定下,边界对齐采用8字节
但是,这只是对于大于八字节的长度类型而言,采用八字节边界
在数据类型低于8字节时,编译器会采用最小符合条件的边界来设定数据起点
这也就是在上述情况下 struct Type_A 的长度为 8 ,struct Type_B的长度为12
如果想让这两个结构体只有实际定义长度,我们必须通知编译器,改变边界对齐方式
方法一:
预编译命令 : #pragma pack( [ n ] ) 与 #pragma pack( )
#pragma pack( [ n ] ) 通知编译系统,以下代码采用 n字节长的边界 ,n = 1 , 2 , 4 , 8 , 16
#pragma pack( ) 通知编译系统,结束上面的边界对齐方式,以下采用默认方式
方法二:
VC IDE 中设定编译命令:
[Project]|[Settings],[c/c++]选项卡[Category]的[Code Generation]选项
[Struct Member Alignment]中修改,默认是8字节。
我个人不推荐使用IDE的修改,IDE的修改,意味着全部编译代码均采用这种新的边界对齐方式,会对某些代码产生影响
(预编译命令#pragma 的内容比较多,可以参考MSDN中#pragma的详细说明,eg: #pragma comment( lib , "ws2_32.lib" ) )
*/
/*
// ================== 指针与逗号语句 ==================
#define HOST_c2l( c , l ) ( l = ( ( ( unsigned long ) ( *( ( c )++ ) ) ) << 24 ) , \
l |= ( ( ( unsigned long ) ( *( ( c )++ ) ) ) << 16 ) , \
l |= ( ( ( unsigned long ) ( *( ( c )++ ) ) ) << 8 ) , \
l |= ( ( ( unsigned long ) ( *( ( c )++ ) ) ) ) , \
l )
int main( int argc , char* argv[ ] )
{
unsigned char m_caData[ 256 ] ;
unsigned char *m_cpPoint ;
unsigned int *m_ipPoint ;
int i , l ;
m_cpPoint = m_caData ;
m_ipPoint = ( unsigned int * ) m_caData ;
printf( "\n SIZEOF_POINT " ) ;
printf( "\n CHAR_SIZEOF : %08x - INT_SIZEOF : %08x " , sizeof( m_cpPoint ) , sizeof( m_ipPoint ) ) ;
for( i = 0 ; i < 256 ; i++ )
{
m_caData[ i ] = ( i + 1 ) % 0xff ;
}
printf( "\n\n POINT START " ) ;
printf( "\n CHAR %p : %08x - %08x " , m_cpPoint , m_cpPoint , *m_cpPoint ) ;
printf( "\n INT %p : %08x - %08x " , m_ipPoint , m_ipPoint , *m_ipPoint ) ;
printf( "\n\n POINT + 4 " );
m_cpPoint += 4 ;
m_ipPoint += 4 ;
printf( "\n CHAR %p : %08x - %08x %08x ", m_cpPoint , m_cpPoint , *m_cpPoint , *m_cpPoint + 6 ) ;
printf( "\n INT %p : %08x - %08x %08x ", m_ipPoint , m_ipPoint , *m_ipPoint , *m_ipPoint + 6 ) ;
printf( "\n\n , COMMAND AND CHAR_POINT MOVEMENT : " ) ;
i = HOST_c2l( m_cpPoint , l ) ;
printf( "\n CHAR %p : %08x " , m_cpPoint , *m_cpPoint ) ;
printf( "\n\n I : %08x L: %08x \n\n" , i , l ) ;
return 0 ;
}*/
/*
1. printf( "%p" , XXXX ) 输出为XXXX的地址
2. 指针本身只是一个地址入口(四字节) , 任意指针 sizeof( p ) 只有一个结果 : 4 .
3. *p 为指针内容,p 为指针地址
4. 整型指针指向字符型缓冲区时,采用intel数据排列顺序,以四字节为单位进行反序
5. ( p + 1 ) 表示根据指针定义的单元长度加1,若定义为字符指针,则只加一字节,若为整型,则加四字节
6. 逗号语句的计算顺序为自左向右,逗号语句的值为逗号语句的最后一个分量的值.
7. 指针C在每个逗号后就指向下一个字符,运算后,C的指针值变为 C + 4 .
8. l的值每个都在变化,最后的l,即四个字符的并,做为这个表达式的值,可以直接做为赋值使用.
*/
/*
// ================== 指针应用 ==================
#include "malloc.h"
#pragma pack ( 1 )
typedef struct MY_IP_HEAD { unsigned char Ver_Len ;
unsigned int Source_IP ;
unsigned int Dest_IP ; } *IPHEAD , TEST_LEN;
#pragma pack ( )
int main( int argc , char* argv[ ] )
{
char *Buf ;
unsigned char *Point ;
TEST_LEN test ;
IPHEAD ip_point ;
int i , j ;
Buf = ( char * )malloc( 1 << 20 ) ;
for( i = 0 ; i < 1000 ; i++ )
{
Buf[ i ] = i + 1 ;
}
Point = (unsigned char * ) Buf ;
for( i = 0 ; i < 10 ; i++ )
{
ip_point = ( IPHEAD ) Point ;
printf( "\n STEP : %2d" , i ) ;
printf( "\n Buf : " ) ;
for( j = 0 ; j < 10 ; j++ )
{
printf( "%02x " , Point[ j ] ) ;
}
printf( "\n IP : " ) ;
printf( "VER : %02x S : %08x D : %08x " ,
ip_point->Ver_Len , ip_point->Source_IP , ip_point->Dest_IP ) ;
Point += 60 ;
}
free( Buf ) ;
Buf = NULL ;
return 0 ;
}*/
/*
指针是地址,指针也只是地址,这点一定要牢记
在这里,我们采用一个简单的例子,一个无符号的字符串指针,一个IP包的结构体指针
第一个指针赋值之后,会强制将有符号字符变为无符号字符,没有任何额外的开销(告别&0xff了)
第二个指针赋值之后,当我们引用指针时,会自动将字符数据变成整型了( 告别了(l=(a<<24)+(b<<16)+(c<<8)+d )这种有时间开销的语句了)
美中不足的是,又是intel数据排列方式的问题,四个字节做了一个反序
与此类似的就是,结构体的位段,采用的是从右向左的编排顺序
*/
/*
// ================== 死循环 ==================
//#include "windows.h"
int main( int argc , char* argv[ ] )
{
while(1)
{
// ::Sleep(1); //停顿1/1000秒
}
return 0 ;
}
*/
/*
很多时候,我们工作中会用到死循环
在这种情况下,CPU的占用率会非常高,表现就是明显感觉机器运行很慢
双核情况下,占有率一般都超过50%,估计在单核情况下,就是100%(没单核CPU测试)
解决方案:
添加头文件包含: #include "windows.h"
在循环体内加入下列代码: ::Sleep(1); //停顿1/1000秒
这种处理之后,一般双核的CPU,资源占用率不会超过15%
*/
/*
// ================== 文件系统 ==================
#define FILE_NUMEBER ( 2000 )
int main( int argc , char* argv[ ] )
{
FILE *f ;
char name[ 256 ] ;
int i ;
for( i = 0 ; i < FILE_NUMEBER ; i++ )
{
sprintf( name , "%04d.txt" , i ) ;
if( ( f = fopen( name , "w" ) ) == NULL )
{
printf("\n Can't Creat File %s " , name ) ;
return 0 ;
}
fprintf( f, " %04d" , i ) ;
fclose( f ) ;
}
return 1 ;
}
*/
/*
在win系统中,我们进入文件夹后,win文件系统会对这个文件夹下的文件进行一次遍历以构造一个数据表
显然,文件数目越多,时间越长(要不停地开内存来放数据,重构数据表)
运行上面的代码后,打开那个文件夹,你会发现,系统很慢,如果不慢,FILE_NUMEBER 后面的数字改成20000
一般建议一个文件夹下面最多放1000个文件
我最近碰到一兄弟,一文件夹下面放2W个文件!!
机器的情况是:系统不稳定,老死机(不死才怪,一开这个文件夹,win不崩溃都算是奇迹了)
*/
/*
// ================== 文件处理效率 ==================
#include "time.h"
// test.txt 一个长度不小于31M字节的数据文件
// 定义缓冲区长度 31 M
char buf[ 31 << 20 ] ;
// 测试开关,设置则为测试块读,不设置则为字节读
#define TEST_BLOCKREAD
#ifdef TEST_BLOCKREAD
#define RUM_TIMES ( 200 )
#else
#define RUM_TIMES ( 2 )
#endif
int main( int argc , char* argv[ ] )
{
FILE * f;
clock_t tima,timb ;
int i , j ;
if( ( f = fopen( "test.txt" , "rb" ) ) == NULL)
{
printf( "\n Can't Open The File " ) ;
return 0 ;
}
tima = clock( ) ; // clock() : 以千分之一秒为计数,返回当前cpu时间
for( j = 0 ; j < RUM_TIMES ; j ++)
{
#ifdef TEST_BLOCKREAD
fread( buf , 1 , 31 << 20 , f ) ;
#else
for( i = 0 ; i < ( 31 << 20 ) ; i++ )
{
fscanf( f , "%c" , &buf[ i ] ) ;
}
#endif
fseek( f , SEEK_SET , 0l ) ;
}
timb = clock( ) ;
fclose( f ) ;
#ifdef TEST_BLOCKREAD
printf( "BLOCK_RUN 200 Times : %d \n" , timb - tima ) ;
#else
printf( "BYTE_RUN 200 Times : %d \n" , ( timb - tima) * 100 ) ;
#endif
return 0 ;
}
*/
/*
我的测试结果:
BLOCK_RUN 200 Times : 7812
BYTE_RUN 200 Times : 670300
这段代码可以清楚的告诉我们,在进行文件操作时,字节方式与块方式到底有多大的效率差别
一般情况下,两者处理同样的数据,块读写要比字节读写快100倍
如果数据读写在核心代码段外,这个差别还不大,只是一个时间的叠加
如果数据读写在核心代码段内,这时候,算法效率是时间的乘积(也就是说,仅仅是文件操作方式的改变,代码的运行效率就要差100倍)
不要小看这100倍,很多时候,数据处理都是一种极限问题.
如果说,做一次数据处理用一天的话,没人会有反对意见,但是,要是做两年呢,我看就没人会同意
建议在做大数据量处理时,先尽可能多的把数据一次读入,然后自己编写解析代码
*/