我可能有一个简单的C程序员在那里!
我试图创建一个简单的C函数,该函数将执行系统命令in
并将过程输出写入字符串缓冲区out
(应初始化为长度n
的字符串数组)。输出需要按以下方式格式化:
写入标准输出的每一行都应该初始化为字符串。每个字符串的长度都是可变的。输出应该是由每个字符串组成的数组。没有办法知道有多少字符串会被写入,所以这个数组在技术上也是可变长度的(但出于我的目的,我只是在函数之外创建了一个固定长度的数组,并将其长度作为参数传递,而不是去创建一个我必须手动分配内存的数组)。
这是我现在的数据:
#define MAX_LINE_LENGTH 512
int exec(const char* in, const char** out, const size_t n)
{
char buffer[MAX_LINE_LENGTH];
FILE *file;
const char terminator = ' ';
if ((file = popen(in, "r")) == NULL) {
return 1;
}
for (char** head = out; (size_t)head < (size_t)out + n && fgets(buffer, MAX_LINE_LENGTH, file) != NULL; head += strlen(buffer)) {
*head = strcat(buffer, &terminator);
}
if (pclose(file)) {
return 2;
}
return 0;
}
用
命名#define N 128
int main(void)
{
const char* buffer[N];
const char cmd[] = "<some system command resulting in multi-line output>";
const int code = exec(cmd, buffer, N);
exit(code);
}
我相信上面的代码导致的错误是一个segfault,但我没有足够的经验来弄清楚为什么或如何修复。
我几乎肯定这是我的逻辑:
for (char** head = out; (size_t)head < (size_t)out + n && fgets(buffer, MAX_LINE_LENGTH, file) != NULL; head += strlen(buffer)) {
*head = strcat(buffer, &terminator);
}
我认为这是:
- 获取
out
的可变引用(即head
指针) - 保存当前标准输出行到
buffer
(通过fgets
) - 附加一个空终止符到
buffer
(因为我不认为fgets
这样做?) - 用步骤3中的值覆盖
head
指针处的数据 - 移动
head
指针strlen(buffer)
字节(即buffer
中char
秒的数量) - 继续直到
fgets
返回NULL
或head
指针已经移动到out
数组的边界之外
我错在哪里?任何帮助感激,谢谢!
编辑# 1
根据Barmar的建议,我编辑了我的代码:#include <stdio.h>
#include <stdlib.h>
#define MAX_LINE_LENGTH 512
int exec(const char* in, const char** out, const size_t n)
{
char buffer[MAX_LINE_LENGTH];
FILE *file;
if ((file = popen(in, "r")) == NULL) return 1;
for (size_t i = 0; i < n && fgets(buffer, MAX_LINE_LENGTH, file) != NULL; i += 1) out[i] = buffer;
if (pclose(file)) return 2;
return 0;
}
#define N 128
int main(void)
{
const char* buffer[N];
const char cmd[] = "<system command to run>";
const int code = exec(cmd, buffer, N);
for (int i = 0; i < N; i += 1) printf("%s", buffer[i]);
exit(code);
}
虽然我写的东西有很多冗余,现在已经修复了,但这仍然会在运行时导致分段错误。
专注于编辑过的代码,这个作业
out[i] = buffer;
有问题。
在这个表达式中,buffer
被隐式地转换为指向其第一个元素的指针(&buffer[0]
,参见:衰变)。不分配额外内存,也不复制字符串。
每次迭代重写buffer
。循环结束后,out
的每个有效元素将指向相同的内存位置,该位置将包含最后一行读取的内容。
buffer
是exec
函数的本地数组。它的生命周期在函数返回时结束,因此main
中的数组包含悬空指针。使用这些值是未定义行为。
另外
for (int i = 0; i < N; i += 1)
总是循环到可存储的最大行数,当可能读取的行数少于这个数时。
刚性解决方案使用数组的数组来存储读取的行。这里是一个粗略的例子(有关使用多维数组作为函数参数的更多信息,请参阅:这个答案)。
#include <stdio.h>
#include <stdlib.h>
#define MAX_LINES 128
#define MAX_LINE_LENGTH 512
int exec(const char *cmd, char lines[MAX_LINES][MAX_LINE_LENGTH], size_t *lc)
{
FILE *stream = popen(cmd, "r");
*lc = 0;
if (!stream)
return 1;
while (*lc < MAX_LINES) {
if (!fgets(lines[*lc], MAX_LINE_LENGTH, stream))
break;
(*lc)++;
}
return pclose(stream) ? 2 : 0;
}
int main(void)
{
char lines[MAX_LINES][MAX_LINE_LENGTH];
size_t n;
int code = exec("ls -al", lines, &n);
for (size_t i = 0; i < n; i++)
printf("%s", lines[i]);
return code;
}
使用动态内存是另一种选择。下面是一个使用strdup(3)
的基本示例,缺少健壮的错误处理。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char **exec(const char *cmd, size_t *length)
{
FILE *stream = popen(cmd, "r");
if (!stream)
return NULL;
char **lines = NULL;
char buffer[4096];
*length = 0;
while (fgets(buffer, sizeof buffer, stream)) {
char **reline = realloc(lines, sizeof *lines * (*length + 1));
if (!reline)
break;
lines = reline;
if (!(lines[*length] = strdup(buffer)))
break;
(*length)++;
}
pclose(stream);
return lines;
}
int main(void)
{
size_t n = 0;
char **lines = exec("ls -al", &n);
for (size_t i = 0; i < n; i++) {
printf("%s", lines[i]);
free(lines[i]);
}
free(lines);
}