首页 | 论坛 | 新闻 | 相册 | 圈子 | 博客 | 网店 | 考研 | FTP | WAP | 游戏 | 竞猜 | 同济大学 博客 | 添加到收藏夹 | RSS 订阅全部版块
同济闲话 | 朝夕问道 | 同舟共济 | 就业与实习 | 置换信息 | 高考招生 | 文艺天地 | 大众影像 | 同济之痒 | 德国留学 | 情感空间 | 旅游户外 | 校园布告栏 | 原创艺术与设计
选课交流 | 有问有答 | 外语学习 | 欧美澳留学 | 影音天地 | 大一生活 | 兼职快报 | 动漫家园 | 游戏人生 | 体育动力 | 精致生活 | 吃在同济 | 上班族 | 土木建筑 | 万有科学
同济大学 同济社团 | 科幻 | 街舞 | 武术 | 吉他 | 竹笛 | 网球 | 棒垒球 | 职业发展 | 辩论演讲 | 轮滑滑板 | 古典乐韵 | 合唱团



求职交流版-面经、笔经、求职问答| 同济网团队招募| 同济大学 鹊桥,LOVE.TONGJI.NET



[同舟共济] 版面密码(试行)为 tj | [鹊桥版]-[竞猜吧]-[小游戏] | 在本论坛刊登广告
 24 12
学习工作信息区 | 生活信息区 | 学习学术区 | 生活娱乐区 | 社团活动 | 学院讨论 | 版务管理
发新话题
同济大学2010年迎接新生“小红帽”活动志愿者招募 打印

初学C语言的人最容易犯的错误集锦

引用:
原帖由 OwnWaterloo 于 2008-11-8 16:15 发表
这例子还是有点问题, 在输入超过缓冲区大小后, 不会溢出, 但是会输入到下一个name中。
稍后改正 ……
char * fgets ( char * str, int num, FILE * stream );  是带有缓冲区长度检查的C/C++标准库函数

强调标准,意思是MSVC从7还是8开始, 提供了一套CRT安全版本,全都带有缓冲区长度检查。

首先, 绝对不能使用不带缓冲区长度检查的函数即使它们是标准库函数
如果标准库函数中剩下的函数不能完成工作, 即使牺牲可移植性, 也要使用带有缓冲区检查的非标准扩展函数, 如上面提到的。


详细文档可以见
http://www.cplusplus.com/reference/clibrary/cstdio/fgets.html



这里对它的一些特点作点解释:

一. num 是缓冲区大小
char buf[ n ];
fgets(buf, sizeof(buf)/ sizeof(buf[0]) /* 不需要减1 */ , stdin ); 是可以的, 不会溢出

二. 终止行为
当fgets被调用时, 会从stream读取字符填充到str中, 直到以下情况之一:
1. 读取到换行符 '\n' , fgets将其填入str,并且继续填充1个 null-character '\0', 然后返回str
2. 已经读取了 num-1个非换行字符, (也就是 str[num-2] 被填充了) fgets将填充 str[num-1] = '\0'; 并返回 str
3. 文件结束 ,返回0,表示错误

也就是说, (只要调用者传参正确) 该函数绝对不会溢出缓冲区
在没有发生错误的时候,保证str是一个 c-style-string不丢弃换行符


三. 对于stdin
stdin相对于其他FILE* 有所区别, stdin一般不会end of file, (允许ctrl+z中断,并且输入ctrl+z ,或者重定向)
当stdin发生end of file时, 会将控制交给console, 继续读入数据。

所以需要注意 fgets(str,num, stdin); 之前是否残留有字符,之后是否残留有字符。

比如:
/* 1 */  printf("input an integer: ");
/* 2 */  scanf("%d",&i);
/* 3 */  printf("input a string: ");
/* 4 */  fgets(buf, sizeof(buf)/sizeof(buf[0]), stdin);
/* 5 */  printf("integer=%d string=\"%s\"\n",i,buf);

假设在 2 处 的scanf导致控制转到console, 用户输入一个数之后, 必须敲回车, 完成输入, 就将在stdin中残留字符(0个或多个字符,1个换行符)。
而 4 处的fgets 取出的是这部分残留字符, 而不会将控制再次转到console, 这也许并不是希望的结果。

/* 6 */  printf("input 1st person's name: ");
/* 7 */  fgets(buf, sizeof(buf)/sizeof(buf[0]), stdin);
/* 8 */  printf("input 1st person's name: ");
/* 9 */  fgets(buf, sizeof(buf)/sizeof(buf[0]), stdin);

假设 7 处的fgets导致控制转到console, 用户输入的字符串长度超出buf大小, fgets将只取前一部分, 并且遗留字符在stdin中。
而 9 处的fgets不再将控制转到console, 而是直接取出上次遗留的字符。
也就是引用部分提到的问题,也许仍然不是希望的结果。



四. 怎么办?

“要将input做得very robust是很繁琐的。” —— 烧饼
确实是这样 ……

在C++中, 可以利用 std:: string, std:: getline, std:: istringstream 做到robust。
而C就更繁琐了 ……

下面也许是一个比较灵活的方案。

typedef int ( * getline_callback )(const char* str,size_t len, int more, void* data );

int getline_c_version( getline_callback callback , FILE* stream,void* data) {
  assert( callback && stream && "pre condition failed" );

  char buf[ N ];
  char* r = 0;
  size_t len = 0;
  int more = 0;
  int cont = 0;
  for ( ; ; ) {
    if (0==fgets( buf, sizeof(buf)/sizeof(buf[0]), stream ) )
      return EOF;
    len = strlen( buf );  /* 这里我觉得是fgets的一个严重失误, 如果设计成返回被填充的最后一个字符, 就立刻能得到长度,同时也可以返回0表示EOF, 为什么需要长度下面会说明 */
    assert( 1<=len ); /* 在没有数据的时候 fgets将返回EOF,否则 至少有一个 '\n' */
    more = (int)buf[ len-1 ] - (int)'\n';  /* 必须求出长度,测试最后一个字符是否是换行,才知道是读取完整行,还是缓冲区不够被截断 */
/* 在其他很多时候,也是需要长度的,比如马上需要malloc来复制一份。 而且fgets内部必然是知道长度的,为什么不返回给调用者呢 ……  */

    cont = callback( buf, len, more, data ) /* 回调,用户能得到字符串首地址,长度,该行是否有更多字符,以及用户传入的数据 */

   if ( !more ) /* 读取完毕 */
      return GET_WHOLE_LINE;
   if ( !cont )  /* 回调可以返回0 ,表示希望终止而非继续读取整行 */
      return USER_ABORT;
  }

  assert( !"shouldn't get here" );
  return ERROR;
}


之后可以定义一个 :
int ignore(const char* /*str*/, size_t /*len*/, int /*more*/, void* /*data*/ ) {
  return 1;
}
getline_c_version( ignore, stdin, 0 ); 就可以插在上面的 /* 2 */ 和 /* 3 */  以及 /* 8 */ 和 /* 9 */之间。




对于需要固定长度的用户, 可以选择只取前面部分 :

typedef struct get_first_data_tag {
  char* dst;
  size_t len;
  int get;
} get_first_data;
int get_first(const char* str, size_t len, int /*more*/, void* data ) {
  get_first_data* p = data;
  if ( !p->get ) {
    p->get = 1;
    if ( p->len < len ) {
       strncat( p->dst, str, p->len-1);
       p->dst[ p->len-1 ] = '\0';
    }
    else {
       strncat( p->dst, str, len );
       p->len = len;
    }
  }
  return 1;
}

char str[10];
get_first_data d;
d.dst = str;
d.len = 10;
d.get = 0;
getline_c_version( get_first, stdin, &d );


char* str = malloc( 20 );
get_first_data d;
d.dst = str;
d.len = 20;
d.get = 0;
getline_c_version( get_first, stdin, &d );
free( str );






如果需要取得整一行, 肯定需要用C实现一个可动态增长的结构:

typedef struct get_whole_data_tag {
  std:: string s; // 由于功底薄弱 ……  这里用C++表示 ……
} get_whole_data;

int get_whole(const char* str, size_t len, int more, void* data ) {
  get_whole_data* p = data;
  p->s += str;
  return 1;
}

get_whole_data d;
getline_c_version( get_whole, stdin, &d );
// do some work with d.s.c_str()

[ 本帖最后由 OwnWaterloo 于 2008-11-12 03:16 编辑 ]


引用:
原帖由 水竹院落 于 2008-11-8 15:59 发表

f(int n, int a[][n])    <-- C99 标准所支持的,gcc4/vs2008 可以通过编译,而 gcc3/vc++6.0/vs2003 之类的不行~~
一. gcc应该是从3开始支持C99的。

需要加入参数
gcc -std=c99 file.c ...

应该是LS的某个使用gcc3的IDE默认没有加这个参数, 而某个使用gcc4的IDE默认使用了这个参数。



二. MSVC倒是一直不支持C99 ……  它对C++标准的支持都进展缓慢 …… 热衷于.Net, C#, C++/CLI ……



三. C99 确实加入了 运行时定长数组 (姑且这么叫吧 , 官方名字叫什么不了解 …… C的资料貌似很少???)

但这真的有用吗?

1.  第1个对比 :

void f1_C99 (size_t size) {
  /* size 是一个运行时的值 */
  char arr[ size ] = {0};
  /* 将arr作为buf传递给某些函数,比如fgets */
  fgets( arr, size, stdin );
}

void f1_C89_Cplusplus (size_t size ) {
  char* arr = alloca( size );
  fgets( arr, size , stdin );
}

void f1_Cplusplus_only (size_t size) {
  std::vector<char> arr( size );
  fgets( &arr[0], size, stdin );
}


2. 总觉得C99的写法让人心里很慌 ……
怎么说呢 ……

假设刚才3个函数里的arr的元素不是char (毕竟只是为了使用fgets作为说明,才使用char的) 而是int。

fC99( -1212 );
fC99( -326 );

会怎样?


这里的动机太明显, 举个不那么明显的例子:

size_t s = 0;
sscanf("%u",&s);
fC99( s );



负数转型为unsigned会变得很大, 使得栈溢出,而这样有用吗?

int s = 0;
sscanf("%d", &i );
if (s > 0)
  fC99( s );

本质问题并不是负数, 而是很大。 使用int 同样不能解决问题。 必须 :

if ( (double)s * sizeof( some_type_inside_fC99 /* how to know? */ < (double) stack_limit - current_used )
  fC99( s );



3. 其实这是(也许不是)个很严重的问题 ……
因为C99的这个特性,  深刻体会到以前写的 f1C89_Cplusplus 和 f1Cplusplus_only 同样可能出现这种情况 ……

try {
  f1Cplusplus_only( -1212 );
}
catch ( std::bad_alloc& )  {
  assert( "low memory, need to test and debug this program");
}

__try {
  f1C89_Cplusplus( -1212 );
}
__except( EXCEPTION_STACK_OVERFLOW==GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) {
  assert( "stack overflow, need to test and debug this program");
}

也许会有一点帮助, 也许没有 ……
在f1C99 在我的机器上跑崩掉之后,  不敢试了 ……
改天装个虚拟机做测试 ^_^



4. 多维数组参数问题

f(int n, int a[][n])  是不行的, 虽然编译可以通过。
要  f(int rows,int cols, int arr[ rows ][ cols ] );  才可以。


具体什么意思 ?
引用:
原帖由 水竹院落 于 2008-11-8 15:59 发表
复制内容到剪贴板
代码:
void f(int n, int a[][n]){
    // 可以直接用多维数组作为参数
}
LS考虑下怎么使用a, 就明白了。



既然如此, 第2个对比 :

void fC99(int rows,int cols,int a[ cols ][ rows ] ) {
  for (int i=0; i != rows; ++i )
    for (int j=0; j != cols; ++j )
      printf("%d ", a[ j ] [ i ] );
}

void fC89_Cplusplus( int rows, int cols, int* arr ) {
  for (int i=0; i != rows; ++i )
    for (int j=0; j != cols; ++j )
      printf("%d", a[ i * cols + j  ] );  /* 与 a[ j ] [ i ] 相比,效率应该是相同的 (前者做的其实和后者是同样的事) */
}

void fCplusplus_only( std:: vector< std:: vector< int > > & arr ) {
   for (size_t i = 0; i != arr.size(); ++i )
     for (size_t j=0; j != arr[ i ].size(); ++j );  // 每维的长度不一定相同
       std:: cout << arr[ i ][ j ] << " ";
}

[ 本帖最后由 OwnWaterloo 于 2008-11-12 04:17 编辑 ]


回复:

之所以想说 C99,是因为上课的时候,zhm 老师说多维数组是不可以直接放函数参数的........
而且还硬要在函数里头使用 a[I][J] 的写法........


ps: http://www.cplusplus.com  <--- 我也常去的!感觉比 msdn 更加接近我心目中的标准...~~



.
.
.
nihui's blog
.
.
.
C99 的这个改进  让语法方便一点。
实质还是没有改, 多维数组其实依然是一维的。

所以C89 里面常用的写法是 a[ i * cols + j ]
如果非要写 a[ i ] [ j ]    要强转一下 ……


那个网站的reference不错的 ~~~
查询C++的资料话,  感觉比MSDN更标准。 几乎就是按照ISO C++03来的。
MSDN搜CRT, 一大堆非标准扩展 ……
但是那个网站好像缺了numeric 和 cwchar ……


 24 12
※ 本贴中一切内容均为发帖帐号所有人自行发布,同济网不承担任何法律及道德责任。
   ‹‹ 上一主题 | 下一主题 ››


发新话题


同济大学  - 同济网Tongji.Net  - 同济大学 论坛  - 同济大学BBS  - 同济论坛  - 同济BBS  - 同济大学 高考  - 同济大学 考研  - 同舟共济 - 同济大学图书馆 - 同济大学招生网 - 同济大学研究生院
Police