读取因EFAULT而失败

时间:2017-09-16 14:43:26

标签: c linux

我正在运行以下C代码,其中尝试读入缓冲区 在调用者的堆栈上分配,但在错误14(错误地址)时失败。

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

void wrapper(int fd, char **buf)
{
  int res = read(fd, *buf, 10);

  printf("res: %d, errno: %d\n", res, errno);

  printf("Buf: %s\n", *buf);
}

int main()
{
  char buffer[10];

  memset(buffer, 0, 10);

  int fd = open("main.c", O_RDONLY);

  wrapper(fd, (char **)&buffer);

  return 0;
}

输出

res: -1, errno: 14
Buf: (null)

我一直在寻找解释失败的原因,而将其改为

void wrapper(int fd, char *buf)
...
wrapper(fd, (char *)buffer);

有效,但到目前为止没有结果。

2 个答案:

答案 0 :(得分:0)

  

为什么失败

数组不是指针。 buffer不是char*。因此,&buffer不是char**,与char**不兼容,不应转发为char**。如果将其强制转换为char**然后取消引用,则行为未定义。

答案 1 :(得分:0)

在分析了你的意图后,当然可以通过read(2)系统调用创建包含读取字符串的“包装器”,并使用远离wrapper()函数的缓冲区。您希望传递将从文件表中读取的字符数量,该文件表是由open(2)系统调用返回的表(文件描述符)的索引。但正如上午说,数组不是指针,你的解决方案无法正常工作。

让我解释一下我对代码的简单修复:

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#define AMOUNT 20

#define assert_msg(x) for ( ; !(x) ; assert(x) )

void
wrapper(int fd, char **buf, size_t size)
{
        ssize_t res;
        char *out;

        out = calloc(size + 1, sizeof(char));
        assert(out != NULL);

        res = read(fd, out, size);
        assert_msg(res != -1) {
                fprintf(stderr, "Error ocurred: %s\n", strerror(errno));
        }

        out[size] = '\0';
        fprintf(stdout, "Inside function: %s\n", out);

        fprintf(stdout, "res: %d, size: %d, errno: (%d: %s)\n", res, size,
            errno, strerror(errno));

        *buf = out;
}

int
main(int argc, char **argv)
{
        int fd;
        char *buf;

        buf = NULL;
        assert(argc == 2);

        errno = 0;
        fd = open(argv[1], O_RDONLY);
        assert_msg(fd != -1) {
                fprintf(stderr, "Error ocurred: %s\n", strerror(errno));
        }

        wrapper(fd, &buf, AMOUNT);
        fprintf(stdout, "Outside function: %s\n", buf);

        free(buf);

        return (EXIT_SUCCESS);
}
  1. 我传递一个文件名作为输入参数。这对我来说更容易,而不是硬编码。
  2. 正如您所看到的,在我的wrapper()实现中,我为out缓冲区分配内存,其大小我传递的值为size变量。我知道与AMOUNT值相同的值定义为宏,但在任何其他解决方案中都很容易更改。
  3. 然后,我使用read(2)系统调用从open(2)系统调用main()函数返回的文件描述符中读取给定数量的字符,并将其传递给wrapper()
  4. 在该函数结束时,我告诉我要将地址保存到已分配的out缓冲区的开头,我希望*buf指示该地址。它是size + 1 char元素的缓冲区,在堆上分配,而不是在本地堆栈上分配。因此程序在执行期间不能“重用”该地址。声明为int a;struct type name;char tab[10];的变量的每个地址在函数结束后自动“释放”,并且您无权访问它。需要说明的是,您可以访问(例如,将地址中的打印数据保存到指示器中)但您无法确定是否会丢失保存在那里的数据。手动分配的空间仍然存在于堆上,直到调用free(3)函数。
  5. 所以,如果我们做的事情如下:

    void
    wrapper(int fd, char **buf, const size_t size)
    {
            ssize_t res;
            char out[size];
    
            (...)
    
            *buf = out;
    }
    

    在程序执行过程中,您可能会丢失保存在本地堆栈中的数据。

    此外,在我的解决方案中,我还定义了自己的宏assert_msg(x),它能够运行assert(3)函数并显示错误的文本消息。但它只是一个功能,但是由于我们能够看到对应于errno数字的字符串。

    当然,我的程序需要更好的处理错误,但它必须只提出这个想法。

    此外,您还应在使用open(2)系统调用作为第三个参数时指定文件权限。它看起来类似于第二个参数,因为它是一个按位'或'分隔的值列表。示例标记:S_IRUSRS_IRGRPS_IWOTH等 在该参数中,您还可以编写描述权限的正确值,例如0755