现在我尝试提高我对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;
}
Run Code Online (Sandbox Code Playgroud)
主要思想是将所有符号都读到buffer我们见面之前\n.
我们不知道要读取的符号总数,因此使用realloc()函数buffer定期扩展是合理的.
所以,为了扩展buffer我们使用:
char *newBuffer = realloc(buffer, maximumLength += sizeIncrement);
Run Code Online (Sandbox Code Playgroud)
在这种情况下,realloc()返回newBuffer指向扩展缓冲区的指针.
之后,如果realloc()成功调用,currentPosition则重新计算为:
currentPosition = newBuffer + (currentPosition - buffer);
Run Code Online (Sandbox Code Playgroud)
问题:
currentPosition以这种方式重新计算是否有效?
据我所知,realloc()调用buffer指针失效后.(参见,例如,这).对buffer指针的任何访问都会导致未定义的行为.那么......我哪里错了?
此代码导致未定义的行为:
currentPosition = newBuffer + (currentPosition - buffer);
Run Code Online (Sandbox Code Playgroud)
传递指针后realloc,该指针变量(以及基于该指针的所有其他指针)变得不确定,这与未初始化变量具有相同的状态.
参考: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;
}
Run Code Online (Sandbox Code Playgroud)
(就个人而言,我会全程使用偏移,而不是currentPosition完全避免这个问题)