用malloc
创建的已有数组的长度可以通过realloc
函数来调整。realloc
函数的基本知识已经在第2章详细探讨过了。C99标准支持变长数组,有些情况下这种解决方案可能比使用realloc
函数更好。如果没有使用C99,那就只能用realloc
。此外,变长数组只能在函数内部声明,如果数组需要的生命周期比函数长,那也只能用realloc
。
为了说明realloc
函数,我们会实现一个从标准输入读取字符并放入缓冲区的函数,缓冲区会包含除最后的回车字符之外的所有字符。我们无法得知用户会输入多少字符,因此也就无法知道缓冲区应该有多长。我们会用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;
}
首先我们声明了一系列变量,总结在表4-2中。
sizeIncrement |
缓冲区的初始大小以及需要增大时的增量 |
---|---|
buffer |
指向读入字符的指针 |
currentPosition |
指向缓冲区中下一个空白位置的指针 |
maximumLength |
可以安全地存入缓冲区的最大字符数 |
length |
读入的字符数 |
character |
上次读入的字符数 |
缓冲区创建时的大小是sizeIncrement
,如果malloc
函数无法分配内存,第一个if
语句会强制getLine
函数返回NULL
。接着是一次处理一个字符的无限循环,循环退出后,字符串末尾会添加上NUL
,然后返回缓冲区的地址。
在while
循环内部,程序每次读入一个字符,如果是回车符,循环退出。接着,if
语句判断我们有没有超出缓冲区大小,如果没有超出,字符就被添加到缓冲区中。
如果超出了缓冲区大小,realloc
函数会分配一块新内存,这块内存比旧内存大sizeIncrement
字节。如果无法分配内存,我们会释放现有的已分配内存,强制函数返回NULL
;否则currentPosition
会调整为指向新分配的缓冲区。realloc
函数不一定会让已有的内存保持在原来的位置,所以必须用它返回的指针来确定调整过大小的内存块的位置。
newBuffer
变量持有已分配内存的地址,我们需要用别的变量而不是buffer
,这样万一realloc
无法分配内存,我们也可以检测到这种情况并进行处理。
如果realloc
分配成功,我们不需要释放buffer
,因为realloc
会把原来的缓冲区复制到新的缓冲区中,再把旧的释放。如果试图释放buffer
,十有八九程序会终止,因为我们试图重复释放同一块内存。
图4-6说明了getLine
函数面对Once upon a time there was a giant pumpkin这个输入字符串时的内存分配情况。我们简化了程序栈,省略了除buffer
和currentPosition
之外的局部变量。根据包含字符串的方框来看,buffer
增长了四次。
realloc
函数也可以用来减少指针指向的内存。为了说明这种用法,如下所示的trim
函数会把字符串中开头的空白符删掉:
char* trim(char* phrase) {
char* old = phrase;
char* new = phrase;
while(*old == ' ') {
old++;
}
while(*old) {
*(new++) = *(old++);
}
*new = 0;
return (char*) realloc(phrase,strlen(phrase)+1);
}
int main() {
char* word = (char*)malloc(strlen(" cat")+1);
strcpy(word," cat");
printf("%s\n",trim(word));
}
第一个while
循环使用old
变量跳过开头的空白符,第二个while
循环把字符串中剩下的字符复制到字符串的开头,它的判断条件一直是真,直到遇到NUL
字符,就会变成假,接着字符串末尾会添加0。然后我们会根据字符串的长度用realloc
函数调整内存大小。
图4-7说明了该函数接受" cat"
字符串作为参数时的执行情况。字符串在trim
函数执行前后的状态如图所示,阴影部分的内存是旧内存,不应该访问。