I am trying to write a single function that will read a certain number of bytes from a C file, or the entire file, based on the arguments. I want it to be memory-safe of course, and deal with NULL terminators and the like. Not only that, but it should be efficient.#include <stdio.h>#include <stdlib.h>#include <string.h>char *read(FILE *file, long bytes) {    /* This function takes a FILE pointer <file> and reads <bytes> bytes from it. These     * bytes are returned as a char* back to the calling function and should be freed     * since they were malloc'd. This function was an attempt to recreate the     * .read( [bytes] ) method on python file objects */    /**********************************     Set bytes to 0 to read entire file     **********************************/    long fileSize = 0; // File size to zero before byte check    if (bytes == 0) {        fseek(file, 0L, SEEK_END); // seek to end        fileSize = ftell(file); // find size of file by checking end        rewind(file); // back to the beginning for reading    } else {        fileSize = ++bytes; // extra byte for NULL     }    char buffer[16]; // We will read the file 16 bytes at a time    char *file_out = malloc(fileSize + 1);    if (file_out == NULL) {         fputs(Could not allocate enough memory, stderr);         exit(EXIT_FAILURE);    }    while (fgets(buffer, 16, file) != NULL) {        strncat(file_out, buffer, 16);    }    return file_out;}Is this efficient enough for large files and fringe cases?
Memory-safe file-reading in C
c;strings;memory management;file
In addition to the other answers, you're allocating memory using malloc inside your function. This forces the caller to use free to delete the memory block when the caller may want to use a different allocator for memory management. I'd recommend changing your function's signature like this:size_t read(FILE *file, long bytes, char *buffer, int bufferSize) { ... }where buffer is the pointer to the target buffer and bufferSize is the number of bytes in that buffer.This way you don't have to deal with allocating the block that you use to read into - the caller can fully manage it, using any memory-management functions. This frees up your return type from being a pointer (since the caller already knows where data will go) and now you can easily return number of bytes read or -1 on error.