C用realloc调整数组长度

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中。

表4-2:getLine函数的变量

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这个输入字符串时的内存分配情况。我们简化了程序栈,省略了除buffercurrentPosition之外的局部变量。根据包含字符串的方框来看,buffer增长了四次。

enter image description here

图4-6:getLine函数的内存分配

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函数执行前后的状态如图所示,阴影部分的内存是旧内存,不应该访问。

enter image description here

图4-7:realloc示例

赞(1)

评论 抢沙发

评论前必须登录!

 

C指针