我建议使用 getline() 读取每一行,然后使用 sscanf() 或自定义解析函数对其进行解析。
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *linebuf = NULL;
size_t linemax = 0;
ssize_t linelen;
while (1) {
char type[2];
unsigned long addr;
size_t len;
char dummy;
linelen = getline(&linebuf, &linemax, stdin);
if (linelen == -1)
break;
if (sscanf(linebuf, "%1s %lx, %zu %c", type, &addr, &len, &dummy) == 3) {
printf("type[0]=='%c', addr==0x%lx, len==%zu\n", type[0], addr, len);
}
}
/* Optional: Discard used line buffer. Note: free(NULL) is safe. */
free(linebuf);
linebuf = NULL;
linemax = 0;
/* Check if getline() failed due to end-of-file, or due to an error. */
if (!feof(stdin) || ferror(stdin)) {
fprintf(stderr, "Error reading from standard input.\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
上面,linebuf 是动态分配的缓冲区,linemax 是为其分配的内存量。 getline() 没有行长限制,除了可用内存。
因为%1s(单字符标记)用于解析第一个标识符字母,所以忽略它之前的任何空格。 (除%c 和%n 之外的所有转换都会静默跳过前导空格。)
%lx 将下一个标记作为十六进制数转换为unsigned long(与unsigned long int 完全相同)。
%zu 将下一个标记作为十进制非负数转换为size_t。
最后的%c(转换为char)是一个假捕手;它不应该转换任何东西,但如果确实如此,则意味着在线上有额外的东西。它前面有一个空格,因为我们有意在转换后跳过空格。
(scanf()/sscanf() 转换模式中的空格表示在该点跳过任意数量的空格,包括 none。)
scanf 系列函数的结果是成功转换的次数。因此,如果该行具有预期的格式,我们会得到3。 (如果行中有额外的东西,它将是4,因为虚拟字符转换了一些东西。)
此示例程序仅打印出解析后的type[0]、addr 和len 的值,因此您可以轻松地将其替换为您需要的任何if (type[0] == ...) 或switch (type[0]) { ... } 逻辑。
由于行缓冲区是动态分配的,因此最好将其丢弃。我们确实需要将缓冲区指针初始化为NULL 并将其大小初始化为0,以便getline() 将分配初始缓冲区,但我们不一定需要释放缓冲区,因为操作系统会自动释放进程使用的所有内存。这就是为什么我添加了关于丢弃行缓冲区是可选的注释的原因。 (幸运的是,free(NULL) 是安全的,什么也不做,所以我们只需要将free(linebuf) 设置为linebuf 为NULL 和linemax 为0,我们甚至可以重用缓冲区。真的,我们甚至可以在 getline() 之前完全安全地这样做。因此,这是一个很好的动态内存管理示例:没有行长限制!)
要记住每个内存引用以进行某种处理,我们真的不需要做太多额外的工作:
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
struct memref {
size_t addr;
size_t len;
int type;
};
struct memref_array {
size_t max; /* Number of memory references allocated */
size_t num; /* Number of memory references used */
struct memref *ref; /* Dynamically allocated array of memory references */
};
#define MEMREF_ARRAY_INIT { 0, 0, NULL }
static inline int memref_array_add(struct memref_array *mra, size_t addr, size_t len, int type)
{
/* Make sure we have a non-NULL pointer to a memref_array structure. */
if (!mra)
return -1;
/* Make sure we have room for at least one more memref structure. */
if (mra->num >= mra->max) {
size_t new_max;
struct memref *new_ref;
/* Growth policy. We need new_max to be at least mra->num + 1.
Reallocation is "slow", so we want to allocate extra entries;
but we don't want to allocate so much we waste oodles of memory.
There are many possible allocation strategies, and which one is "best"
-- really, most suited for a task at hand --, varies!
This one uses a simple "always allocate 3999 extra entries" policy. */
new_max = mra->num + 4000;
new_ref = realloc(mra->ref, new_max * sizeof mra->ref[0]);
if (!new_ref) {
/* Reallocation failed. Old data still exists, we just didn't get
more memory for the new data. This function just returns -2 to
the caller; other options would be to print an error message and
exit()/abort() the program. */
return -2;
}
mra->max = new_max;
mra->ref = new_ref;
}
/* Fill in the fields, */
mra->ref[mra->num].addr = addr;
mra->ref[mra->num].len = len;
mra->ref[mra->num].type = type;
/* and update the number of memory references in the table. */
mra->num++;
/* This function returns 0 for success. */
return 0;
}
int main(void)
{
struct memref_array memrefs = MEMREF_ARRAY_INIT;
char *linebuf = NULL;
size_t linemax = 0;
ssize_t linelen;
while (1) {
char type[2];
unsigned long addr;
size_t len;
char dummy;
linelen = getline(&linebuf, &linemax, stdin);
if (linelen == -1)
break;
if (sscanf(linebuf, "%1s %lx, %zu %c", type, &addr, &len, &dummy) == 3) {
if (memref_array_add(&memrefs, (size_t)addr, len, type[0])) {
fprintf(stderr, "Out of memory.\n");
return EXIT_FAILURE;
}
}
}
/* Optional: Discard used line buffer. Note: free(NULL) is safe. */
free(linebuf);
linebuf = NULL;
linemax = 0;
/* Check if getline() failed due to end-of-file, or due to an error. */
if (!feof(stdin) || ferror(stdin)) {
fprintf(stderr, "Error reading from standard input.\n");
return EXIT_FAILURE;
}
/* Print the number of entries stored. */
printf("Read %zu memory references:\n", memrefs.num);
for (size_t i = 0; i < memrefs.num; i++) {
printf(" addr=0x%lx, len=%zu, type='%c'\n",
(unsigned long)memrefs.ref[i].addr,
memrefs.ref[i].len,
memrefs.ref[i].type);
}
return EXIT_SUCCESS;
}
新的memref 结构描述了我们读取的每个内存引用,memref_array 结构包含动态分配的数组。 num 成员是数组中的引用数,max 成员是我们为其分配内存的引用数。
memref_array_add() 函数接受一个指向memref_array 的指针,并填写三个值。因为 C 是按值传递函数参数的——也就是说,改变函数中的参数值并不会改变函数中的变量呼叫者! – 我们需要传递一个指针,以便通过指针进行更改,这些更改在调用者中也是可见的。这就是 C 的工作原理。
在那个函数中,我们需要自己处理内存管理。因为我们使用MEMREF_ARRAY_INIT 将内存引用数组初始化为已知的安全值,所以我们可以在需要时使用realloc() 来调整数组指针的大小。 (本质上,realloc(NULL, size) 的工作方式与malloc(size) 完全相同。)
在主程序中,我们在 if 子句中调用该函数。 if (x) 与if (x != 0) 相同,即。如果x 不为零,则执行主体。因为memref_array_add() 成功返回零,错误返回非零,if (memref_array_add(...)) 表示“如果 memref_array_add 调用失败,则”。
请注意,我们根本不会丢弃程序中的内存引用数组。我们不需要,因为操作系统会为我们释放它。但是,如果程序在不再需要内存引用数组后确实做了进一步的工作,那么丢弃它是有意义的。我打赌你猜到这就像丢弃 getline() 使用的行缓冲区一样简单:
static inline void memref_array_free(struct memref_array *mra)
{
if (mra) {
free(mra->ref);
mra->max = 0;
mra->num = 0;
mra->ref = NULL;
}
}
所以在主程序中,memref_array_free(&memrefs); 就足够了。
函数定义前面的static inline 只是告诉编译器在它们的调用位置内联函数,而不是为它们生成可链接符号。如果你愿意,你可以省略它们;我用它们来表示这些是只能在这个文件(或编译单元)中使用的辅助函数。
之所以在主程序中使用memrefs.member,而在辅助函数中使用mra->member,是因为mra是指向结构的指针,而memrefs是结构类型的变量。同样,只是一个 C 怪癖。 (我们也可以写(&memrefs)->member 或(*mra).member。)
这可能比你想读的要多得多(不仅仅是 OP,而是你,亲爱的读者),但我一直认为动态内存管理应该尽早展示给新的 C 程序员,让他们自信地掌握它们,而不是认为它很难/不值得。