首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C安全getline()

C安全getline()
EN

Code Review用户
提问于 2022-12-04 09:18:54
回答 2查看 609关注 0票数 6

对于我最近的一个项目,我必须在没有外部库的情况下用纯C从控制台读取输入(换句话说,我自己编写的代码)。我不喜欢标准格式的输入,比如scanf或C++中的std::cin,我觉得必须做一个虚拟读物才能去掉剩馀的换行符不是很好。在C++中,我可以执行std::string in; std::getline(std::cin, in);来读取整行,但是C没有(至少在我的系统中没有)。从控制台读取输入经常是需要的,所以写了几次之后,我决定我想要一次又一次可以写的东西。这段代码很简单,我认为我没有留下太多的漏洞,但我仍然希望听到关于它的反馈,所以请让我知道我能做些什么来改进它。

代码语言:javascript
复制
#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
代码语言:javascript
复制
#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;
    }
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2022-12-04 23:03:08

让我知道我能做些什么来改善它。

设计缺陷

不知何故,调用方需要区分由于'\n'EOF而立即结束的调用。就像fgets()一样。

也许是三分回报:EOF0 (失败)还是1 (成功)?

设计缺陷2(微妙)

EOF由于文件结束和输入错误而发生.

  • 一些输入,那么文件末尾应该返回true。
  • 没有输入和文件结束时应该返回EOF.(见上文)。
  • 无论输入多少,输入错误都应该返回EOF。(见上文)。

回顾feof()ferror()

类似于:

代码语言:javascript
复制
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)值得注意,特别是如何调用和返回值意味着什么。

分配给引用对象大小,而不是

类型。

更容易编写正确的代码,检查和维护。

代码语言:javascript
复制
//data = malloc(cap * sizeof(char));
data = malloc(sizeof data[0] * cap);

声明和初始化

避免留下未分配的新声明对象。

代码语言:javascript
复制
//char *data;
//.... some time later
//data = malloc(cap * sizeof(char));

char *data = malloc(cap * sizeof(char));

分配一个虚拟

显然,size == NULL是可以的,当NULL重新分配到一个虚拟代码时,以后的代码不需要多个if (size != NULL) ...测试

代码语言:javascript
复制
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

票数 2
EN

Code Review用户

发布于 2022-12-04 13:15:37

关于总体设计:

代码语言:javascript
复制
bool getLine(FILE *src, char **dest, size_t *size);

此函数的调用方式如下:

代码语言:javascript
复制
char*  buff     = NULL;
size_t buffSize = 0;

if (getLine(STDIN, &buff, &size)) 
{
  // do something with buff

  free(buff);
}

这一切都很好,但这给代码带来了很大的负担,以确保缓冲区被释放。问题是,getLine必须能够读取任意长度的输入吗?

或者我们可以设计这样的东西:

代码语言:javascript
复制
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
}

甚至:

代码语言:javascript
复制
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函数中的代码更简单,并减轻了调用方确保释放内存的负担。

代码语言:javascript
复制
    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函数使用相同的方法。即调用错误处理程序,默认情况下调用中止。

代码语言:javascript
复制
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关于分配失败。失败后要做什么的决定应该在调用代码中,而不是这个函数中。

设置大小和数据并不是个坏主意。但在这方面如何进行是值得商榷的。读一些并返回是否有助于调用代码?调用代码如何区分此分配失败和其他问题?释放内存并将所有内容设置为空可能是一个更好的主意。也许使用错误代码可以使调用代码更加清晰。

代码语言:javascript
复制
if (readCount == cap) {
    // The buffer is full, but we still need to shove in the final NUL byte
    [...]
}

这一特殊情况应纳入缓冲区的正常增长,而不是在最后。将下面的条件更改为(readCount + 1) == cap,并移除此特殊处理。

代码语言:javascript
复制
if (newData == NULL) {
    // Failed to resize, shove it in anyway

    data[readCount - 1] = '\0';

    if (size != NULL) *size = readCount;
    return false;
}

我明白您在这里试图做什么;但是如上所述,内存处理是很困难的。值得怀疑的是,该程序是否处于继续运行的状态,这样的黑客攻击是否是一个好主意。

最后,正如@slepic所指出的,fgets是存在的。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/281669

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档