现在我尝试提高我对Richard Reese阅读“理解和使用C指针”指针的认识。
以下是本书中关于realloc()
函数的一个代码示例。
char* getLine(void) {
const size_t sizeIncrement = 10;
char* buffer = malloc(sizeIncrement);
char* currentPosition = buffer;
size_t maximumLength = sizeIncrement;
size_t length = 0;
int character;
if(currentPosition == NULL) { return NULL; }
while(1) {
character = fgetc(stdin);
if(character == '\n') { break; }
if(++length >= maximumLength) {
char *newBuffer = realloc(buffer, maximumLength += sizeIncrement);
if(newBuffer == NULL) {
free(buffer);
return NULL;
}
currentPosition = newBuffer + (currentPosition - buffer);
buffer = newBuffer;
}
*currentPosition++ = character;
}
*currentPosition = '\0';
return buffer;
}
主要想法是在buffer
之前阅读所有符号,直到我们遇到\n
。
我们不知道要读取的符号总数,因此使用realloc()
函数定期展开buffer
是合理的。
因此,要扩展buffer
我们使用:
char *newBuffer = realloc(buffer, maximumLength += sizeIncrement);
在这种情况下,realloc()
返回指向扩展缓冲区的newBuffer
指针。
之后,如果成功调用realloc()
,currentPosition
将重新计算为:
currentPosition = newBuffer + (currentPosition - buffer);
问题:
以这种方式重新计算currentPosition
是否有效?
据我所知,在realloc()
调用buffer
指针失效后。 (例如,见this)。对buffer
指针的任何访问都会导致未定义的行为。那么......我哪里错了?
答案 0 :(得分:6)
此代码导致未定义的行为:
currentPosition = newBuffer + (currentPosition - buffer);
在将指针传递给realloc
之后,该指针变量(以及基于该指针的所有其他指针)变为 indeterminate ,这与未初始化变量具有相同的状态。
参考:C11 6.2.4 / 2:
[...]当指针变为不确定时,指针的值变得不确定 它指向(或刚刚过去)的对象到达其生命周期的末尾。
然后,对无效指针执行指针运算会导致未定义的行为,C11 6.5.6 / 8:
当一个具有整数类型的表达式被添加到指针或从指针中减去时,[...]如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组的最后一个元素的元素对象,评估不得产生溢出;否则,行为未定义
指针操作数当时不指向对象。它曾经指向的对象已经被释放。
实际上,完全评估指针可能会导致未定义的行为,因为不确定的值可能是陷阱表示。 (想象一下,将值加载到地址寄存器中的系统也会执行硬件检查,该地址属于此过程)。参考:C11 3.19.2,6.2.6.1/5:
如果对象的存储值具有这样的表示,并且由不具有字符类型的左值表达式读取,则行为未定义
编写代码的正确方法是:
if(++length >= maximumLength)
{
size_t currentOffset = currentPosition - buffer;
char *newBuffer = realloc(......
// ...
currentPosition = newBuffer + currentOffset;
buffer = newBuffer;
}
(就我个人而言,我会使用偏移而不是currentPosition
来完全避免这个问题)
答案 1 :(得分:4)
是。这就是原因。
currentPosition
和buffer
,因为它们在表达式currentPosition = newBuffer + (currentPosition - buffer);
中使用,只是用于它们的算术值。 realloc
被buffer
取消引用后的任何时间。
当你调用realloc
时,你是正确的,指针必须不再被依赖作为指向缓冲区内存区域的指针。但是,调用不会更改指针中的实际地址值。
答案 2 :(得分:1)
我的五美分。:)
对于初学者,我认为书中显示的程序很糟糕。
它不会检查fgetc
是否返回EOF
。
当遇到文件末尾并且没有读取数据时,它不像往常一样返回NULL
。
此缺点不允许使用该功能,例如以下方式
while ( ( buffer = getLine() ) != NULL )
{
//...
free( buffer );
}
也有太多变数。可以使用较少的变量执行相同的逻辑。
所以我会按照以下方式编写函数
#include <stdlib.h>
#include <stdio.h>
char * getLine( void )
{
const size_t SIZE_INCREMENT = 10;
char *buffer = NULL;
size_t length = 0;
int c = fgetc( stdin );
if ( c != EOF )
{
while ( 1 )
{
if ( length % SIZE_INCREMENT == 0 )
{
char *tmp = realloc( buffer, length + SIZE_INCREMENT );
if ( !tmp )
{
free( buffer );
buffer = NULL;
break;
}
else
{
buffer = tmp;
}
}
if ( c == EOF || c == '\n' ) break;
buffer[length++] = c;
c = fgetc( stdin );
}
if ( buffer ) buffer[length] = '\0';
}
return buffer;
}
int main( void )
{
char *s;
while ( ( s = getLine() ) != NULL )
{
puts( s );
free( s );
}
return 0;
}
如果要输入例如
This is first line
This is second line
然后输出将回显输入
This is first line
This is second line
至于讨论是否该声明
currentPosition = newBuffer + (currentPosition - buffer);
是明确定义的,然而在我看来它是明确定义的。
根据C标准(6.5.6添加剂操作员)
6二元运算符的结果是由此产生的差异 从第一个中减去第二个操作数的。
虽然指针所指向的对象尚未存在,但指针包含相对于引用相同数组元素的指针算法的有效值。 因此,如果要应用子结构,结果将是非活动数组具有的这两个指针之间的元素的有效值。减法运算符只使用整数值进行算术运算,并考虑指针的类型,如
( second_pointer - first_pointer ) / sizeof( *first_pointer )
答案 3 :(得分:0)
代码使用的模式在许多平台上自然会受到任何实现的支持,而这些实现并不会不这样做。遗憾的是,有些实现偶尔会以“优化”的名义破坏这些代码,即使由此获得的任何实际优化的价值与通常由于要求程序员使用替代方案而导致的效率损失相比也会显得苍白无力方法