2023年9月

前言

今天看 cppreference.com 的时候偶然发现这么个有意思的C语言特性(扩展),由于相应的中文资料很少(英文资料也不多),所以决定写这篇文章分享一下。

本文只挑选重要的函数进行介绍。

该扩展为C语言标准中可选实现内容,只有部分编译器能实现(POSIX/Linux)。本文所有内容测试环境如下:

  • archlinux x86_64 (kernel: 6.4.12-zen1-1-zen)
  • gcc 12.2.1 (20230801) (POSIX)
  • glibc 2.38-3

函数定义

以下定义均经过处理,方便阅读。

分配动态内存

FILE *open_memstream (char **bufloc, size_t *sizeloc);
  • 分配动态内存到 bufloc,其大小为 sizeloc 指向的数字。返回的文件指针可用于写入数据。

写入动态内存

size_t getline (char **lineptr, size_t *n, FILE *stream); // 1
size_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *stream); // 2
  1. 传入下列参数调用函数 3。
  2. 读取 stream 中内容直至读取到 delimiter,将读取数据存入 lineptr 指向的内存块并自动扩充该内存块大小,将读取结束后动态内存大小存入 n 指向的数字。
getdelim(lineptr, n, '\n', stream); // getline

分配并写入动态内存

int asprintf (char **ptr, const char *fmt, ...);
  • 以类似于 printf 的方式将格式化内容输出至 ptr 指向的内存块并自动扩充该内存块大小。

示例代码

// #define _GNU_SOURCE // 使 IDE 不要报错

#include <stdio.h>
#include <stdlib.h>
#include <time.h> // time()

int main(void) {
    char *buffer = NULL;
    size_t size = 0;
    FILE *stream;
    size_t read;

    read = getline(&buffer, &size, stdin);
    printf("buffer 中的内容:\n%s大小:%zu\n字符数:%zu\n", buffer, size, read);

    free(buffer);

    stream = open_memstream(&buffer, &size);
    if (stream == NULL) {
        puts("无法打开动态内存数据流。");
        exit(EXIT_FAILURE);
    }

    srand((unsigned int)time(NULL));
    for (int i = 0; i < rand() % 100; i++) {
        fprintf(stream, "%d ", i);
    }
    fflush(stream);
    puts("在一个字符串中的随机个(0~99)数字:");
    puts(buffer);

    free(buffer);

    asprintf(&buffer, "一个随机数:%d", rand());
    puts(buffer);

    free(buffer);
    return 0;
}

原理分析

直接看 glibc 源码。其实就是初始分配 120 字节,当超过上限时 realloc 翻倍。

// glibc/libio/iogetdelim.c  __getdelim
// ...
    if (*lineptr == NULL || *n == 0)
    {
      *n = 120;
      *lineptr = (char *) malloc (*n); // 初始内存大小 120
      if (*lineptr == NULL)
    {
      fseterr_unlocked (fp);
      result = -1;
      goto unlock_return;
    }
    }
// ...
      /* Make enough space for len+1 (for final NUL) bytes.  */ // 考虑字符串末尾 '\0'
      needed = cur_len + len + 1;
      if (needed > *n)
    {
      char *new_lineptr;

      if (needed < 2 * *n)
        needed = 2 * *n;  /* Be generous. */
      new_lineptr = (char *) realloc (*lineptr, needed); // 达到内存上限后再分配一倍内存
      if (new_lineptr == NULL)
        {
          fseterr_unlocked (fp);
          result = -1;
          goto unlock_return;
        }
      *lineptr = new_lineptr;
      *n = needed;
    }
      memcpy (*lineptr + cur_len, (void *) fp->_IO_read_ptr, len);
      fp->_IO_read_ptr += len;
      cur_len += len;
// ...

总结

好像没啥用。(笑)

参考资料

  1. C++ Reference
  2. Open Group Base Specifications
  3. glibc source