K&R实现getline时for循环用i<lim-1而非i<lim的原因
K&R
getLine函数循环边界设计解析 原书实现代码如下:
#define MAXLINE 1000 int getLine(char s[], int lim) { int c, i; for(i = 0; i < lim - 1 && (c = getchar()) != EOF && c!= '\n'; ++i) s[i] = c; if(c == '\n'){ s[i] = c; ++i; } s[i] = '\0'; return i; }
循环使用i < lim - 1判断的核心原因
这个边界设计完全是为了适配C语言的字符串规则,同时避免数组越界:
C语言没有内置的字符串长度标识,所有合法字符串必须以'\0'(空终止符)作为结尾标记,这个标记本身不计算在字符串有效长度内,但必须占用1个元素的数组存储空间。传入的参数lim是字符数组s的总长度,数组可访问的合法索引范围是0到lim - 1,任何对s[lim]及之后位置的读写都属于越界的未定义行为,轻则导致字符串输出乱码,重则破坏栈内存造成程序崩溃。
为什么i < lim无法满足需求
如果把循环条件替换为i < lim,无论哪种场景都会触发数组越界,顺着代码执行逻辑就能推导出来:
- for循环的执行顺序是「判断条件→执行循环体写入字符→i自增」,如果条件为
i < lim,当输入长度足够时,循环会在i = lim - 1时仍然满足判断条件,将读入的普通字符写入s[lim - 1],之后执行++i让i的值等于lim,才会在下一次条件判断时退出循环。 - 循环退出后,代码一定会执行
s[i] = '\0'写入空终止符,此时i = lim,写入位置s[lim]已经超出了数组合法索引范围。 - 就算循环是因为读到换行符提前退出,此时
i的值最多为lim - 1,进入换行处理分支后会先执行++i把i变成lim,后续写终止符时依然会访问s[lim]的越界位置。
i < lim - 1的边界是怎么保证安全的
这个边界刚好给后续的收尾写入预留了足够的合法位置,所有场景下的数组写入都不会超出lim - 1的最大合法索引:
- 如果是因为读入长度达到上限退出循环:此时
i = lim - 1,最后读入的字符不是换行(否则会提前因换行条件退出),不会进入换行处理分支,直接执行s[lim - 1] = '\0',刚好落在数组最后一个合法位置。 - 如果是因为读到EOF或换行符退出循环:此时
i一定小于lim - 1,写入换行、执行i自增之后,i的最大值为(lim - 2) + 1 = lim - 1,最后写入空终止符的位置依然是s[lim - 1],完全在合法内存范围内。
内容的提问来源于stack exchange,提问作者DiamondCorp
相关产品推荐
相关产品推荐

