对于我最近的一个项目,我必须在没有外部库的情况下用纯C从控制台读取输入(换句话说,我自己编写的代码)。我不喜欢标准格式的输入,比如scanf或C++中的std::cin,我觉得必须做一个虚拟读物才能去掉剩馀的换行符不是很好。在C++中,我可以执行std::string in; std::getline(std::cin, in);来读取整行,但是C没有(至少在我的系统中没有)。从控制台读取输入经常是需要的,所以写了几次之后,我决定我想要一次又一次可以写的东西。这段代码很简单,我认为我没有留下太多的漏洞,但我仍然希望听到关于它的反馈,所以请让我知道我能做些什么来改进它。
#ifndef UUTIL_H
#define UUTIL_H
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
bool getLine(FILE *src, char **dest, size_t *size);
#endif#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "uutil.h"
bool getLine(FILE *src, char **dest, size_t *size) {
if (src == NULL) {
if (size != NULL) *size = 0;
return false;
}
if (dest == NULL) {
if (size != NULL) *size = 0;
return false;
}
char *data;
size_t cap = 32;
size_t readCount = 0;
data = malloc(cap * sizeof(char));
if (data == NULL) {
*size = 0;
return false;
}
while (true) {
int readVal = fgetc(src);
if (readVal == EOF || readVal == '\n') {
if (readCount == cap) {
// The buffer is full, but we still need to shove in the final NUL byte
size_t newCap = cap + 1;
char *newData = realloc(data, newCap * sizeof(char));
if (newData == NULL) {
// Failed to resize, shove it in anyway
data[readCount - 1] = '\0';
if (size != NULL) *size = readCount;
return false;
}
cap = newCap;
data = newData;
}
data[readCount] = '\0'; // Do not count the final NUL byte
if (size != NULL) *size = readCount;
*dest = data;
return true;
}
if (readCount == cap) {
// The buffer is full, but we still have more data to shovw in
size_t newCap = cap * 2;
char *newData = realloc(data, newCap * sizeof(char));
if (newData == NULL) {
// Failed to resize, clean up stdin and return
data[readCount - 1] = '\0';
if (src == stdin) while (fgetc(src) != EOF);
if (size != NULL) *size = readCount;
return false;
}
cap = newCap;
data = newData;
}
data[readCount++] = readVal;
}
}发布于 2022-12-04 23:03:08
让我知道我能做些什么来改善它。
不知何故,调用方需要区分由于'\n'和EOF而立即结束的调用。就像fgets()一样。
也许是三分回报:EOF,0 (失败)还是1 (成功)?
EOF由于文件结束和输入错误而发生.
EOF.(见上文)。EOF。(见上文)。回顾feof()和ferror()。
类似于:
if (readVal == EOF) {
...
// Note: `!feof()` subtlely different than `ferror()` here.
if (readCount == 0 || !feof(src)) return EOF;
return 1;
}bool getLine(FILE *src, char **dest, size_t *size)值得注意,特别是如何调用和返回值意味着什么。
类型。
更容易编写正确的代码,检查和维护。
//data = malloc(cap * sizeof(char));
data = malloc(sizeof data[0] * cap);避免留下未分配的新声明对象。
//char *data;
//.... some time later
//data = malloc(cap * sizeof(char));
char *data = malloc(cap * sizeof(char));显然,size == NULL是可以的,当NULL重新分配到一个虚拟代码时,以后的代码不需要多个if (size != NULL) ...测试
size_t dummy;
if (size == NULL) {
size = &dummy;
}上重新分配的特殊代码。
只需在常规循环中使用if (readCount + 1 == cap) {即可。
离开函数时,执行最后的“适当大小”重新分配。
newCap = cap * 2是合理的,但是对于一个非常长的输入,它不会检测到溢出。2)仅限于SIZE_MAX/2。
相反,从31开始,然后是newCap = cap * 2 + 1,代码可以处理到SIZE_MAX。
发布于 2022-12-04 13:15:37
关于总体设计:
bool getLine(FILE *src, char **dest, size_t *size);此函数的调用方式如下:
char* buff = NULL;
size_t buffSize = 0;
if (getLine(STDIN, &buff, &size))
{
// do something with buff
free(buff);
}这一切都很好,但这给代码带来了很大的负担,以确保缓冲区被释放。问题是,getLine必须能够读取任意长度的输入吗?
或者我们可以设计这样的东西:
bool getLine(FILE *src, char *dest, size_t *size);
char buff[1024];
size_t buffSize = 1024;
if (getLine(STDIN, &buff, &buffSize))
{
// do something with buff
}甚至:
size_t getLine(FILE *src, char *dest, size_t size);
char buff[1024];
size_t len = getLine(STDIN, &buff, 1024)
if (len > 0)
{
// do something with buff
}这使得getLine函数中的代码更简单,并减轻了调用方确保释放内存的负担。
if (src == NULL) {
if (size != NULL) *size = 0;
return false;
}
if (dest == NULL) {
if (size != NULL) *size = 0;
return false;
}这是个人品味的问题,但我会将输入验证改为断言。为什么?问题是,如果指针无效为1,则NULL (0)通常不是更好的指示符。忘记处理失败的malloc或从错误的分支调用应该在开发过程中检测到,但在运行时不应该检测。
如果出于某些“安全”原因,必须在运行时尝试检测这些错误,则应采取与XYZ_S函数使用相同的方法。即调用错误处理程序,默认情况下调用中止。
char *newData = realloc(data, newCap * sizeof(char));
if (newData == NULL) {
// Failed to resize, clean up stdin and return
data[readCount - 1] = '\0';
if (src == stdin) while (fgetc(src) != EOF);
if (size != NULL) *size = readCount;
return false;
}处理分配失败是相当困难的。在这里,我不明白,为什么stdin被读取直到EOF关于分配失败。失败后要做什么的决定应该在调用代码中,而不是这个函数中。
设置大小和数据并不是个坏主意。但在这方面如何进行是值得商榷的。读一些并返回是否有助于调用代码?调用代码如何区分此分配失败和其他问题?释放内存并将所有内容设置为空可能是一个更好的主意。也许使用错误代码可以使调用代码更加清晰。
if (readCount == cap) {
// The buffer is full, but we still need to shove in the final NUL byte
[...]
}这一特殊情况应纳入缓冲区的正常增长,而不是在最后。将下面的条件更改为(readCount + 1) == cap,并移除此特殊处理。
if (newData == NULL) {
// Failed to resize, shove it in anyway
data[readCount - 1] = '\0';
if (size != NULL) *size = readCount;
return false;
}我明白您在这里试图做什么;但是如上所述,内存处理是很困难的。值得怀疑的是,该程序是否处于继续运行的状态,这样的黑客攻击是否是一个好主意。
最后,正如@slepic所指出的,fgets是存在的。
https://codereview.stackexchange.com/questions/281669
复制相似问题