为什么我无法写入我使用 calloc 创建的已分配内存?

Why am I not able to write in the allocated memory that I created with calloc?

提问人:digits 提问时间:11/6/2021 最后编辑:digits 更新时间:11/7/2021 访问量:378

问:

我正在制作的 C 代码有问题,我只是找不到根本原因。我试图做的是从任何目录中读取文本文件,并通过使用 calloc 分配内存(以 0 秒为单位)将所有字节放入堆中

问题是,当文件超过一定大小(>=25 KB,可能更小)时,我无法在分配的内存中写入任何内容,但我可以读取文件!

char* ReadBytesFromFile(string fileName[]) {
FILE* fileStream = (FILE*)fopen(fileName, "r");
const fSz fileSizeInBytes = GetFileSizeInBytes(fileName);
char* bytes;
char byteFromFile = 0;

do
{
    bytes = (char*)calloc((size_t)fileSizeInBytes + 1, sizeof(char));
} while (bytes == NULL);


for(long i = 0; i < fileSizeInBytes; i++)
{
    byteFromFile = (char)getc(fileStream);
    if (byteFromFile == EOF) break;
    bytes[i] = byteFromFile;
}

fclose(fileStream);
free(fileStream);

return bytes;
}

顺便一提:

typedef const char      string;
typedef signed long int fSz;

另一个线索是,我在没有 IDE 的情况下使用 GCC 11.2.0 编写此内容,并带有以下标志(在 Makefile 中):

_compilerOptions = -O0 \
                    -pedantic \
                    -Wall \
                    -Wextra \
                    -Wdiscarded-qualifiers \
                    -Wwrite-strings \
                    -ggdb3 \

我知道代码还不“安全”,但我不知道是什么导致了这个错误。 当我调试时,我通过打印 byteFromFile 一次查看每个字符,但是当我打印字节时,没有写入任何内容。

文件大小是我唯一的线索,因为代码适用于大约 1-5 KB 的文件,但大于此大小的文件不起作用。

有什么想法或我做错了什么?:(


更新1:

我更新了代码如下:

char* ReadBytesFromFile(string fileName[])
{
    FILE* fileStream = (FILE*)fopen(fileName, "rb");
    const fSz fileSizeInBytes = GetFileSizeInBytes(fileName);
    fSz counter = 0;
    char* bytes;
    int byteFromFile = 0;

    do
    {
        bytes = (char*)calloc(fileSizeInBytes + 1, sizeof(char));
    } while (bytes == NULL);
    
    while( (byteFromFile = fgetc(fileStream)) != EOF )
    {
        bytes[counter++] = (char)byteFromFile;
    }

    fclose(fileStream);
    free(fileStream);

    return bytes;
}

但我仍然遇到同样的问题(我正在使用 GDB):Empty array


更新2:

我用于测试的文件可以使用以下代码创建:

FILE* fp = (FILE*)fopen("test.txt", "wb");
    char byte = 0x0U;
    
    for(int i = 0; i < 4095; i++)
    {
        (void)putc(byte, fp);
        byte++;
    }

    fclose(fp);

我期望看到的是数组字节中的文件内容。使用代码生成我用作示例的文件,我应该看到:“\000\001\002\003\004\005\006\007\008\009\010\011\ ...\255\000\001\002...” 根据调试器的不同,GDB 以八进制形式打印该信息,但我是在 dec 编写的。

上传整个代码很复杂,因为被拆分为多个文件。但基本上它应该是这样的:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "main.h"
#include "fileRW.h"

/* ****************************** Typedefs ****************************** */
// Char/strings
typedef unsigned char       uchar;
typedef const char          string;

// Numerical
typedef unsigned char       uint8;
typedef unsigned short      uint16;
typedef unsigned int        uint32;
typedef unsigned long       uint64;

typedef signed char         BYTE;
typedef signed short        WORD;
typedef signed int          DWORD;
typedef signed int long     QWORD;

typedef unsigned char       uBYTE;
typedef unsigned short      uWORD;
typedef unsigned int        uDWORD;
typedef unsigned int long   uQWORD;

typedef signed long int     fSz;

/* ****************************** Function Prototypes ****************************** */
off_t GetFileSizeInBytes(string fileName[]);
char* ReadBytesFromFile(string fileName[]);
void ConvertByteToHexFromFile(uBYTE* inputChar);

int main(int argc, char* argv[])
{
    if(argc > 1)
    {
        for(int i = 1; i < argc; i++)
        {
            (void)ReadBytesFromFile(argv[i]);
        }
    }
    return 0;
}

off_t GetFileSizeInBytes(const char fileName[])
{
    struct stat st;

    if(stat(fileName, &st) == 0)
    {
        return st.st_size;
    }
    else
    {
        return -1;
    }
}

char* ReadBytesFromFile(string fileName[])
{
    FILE* fileStream = (FILE*)fopen(fileName, "rb");
    const fSz fileSizeInBytes = GetFileSizeInBytes(fileName) + 1;
    fSz counter = 0;
    char* bytes;
    int byteFromFile = 0;

    do
    {
        bytes = (char*)malloc(fileSizeInBytes * sizeof(char));
    } while (bytes == NULL);
    
    while( (byteFromFile = fgetc(fileStream)) != EOF )
    {
        bytes[counter++] = (char)byteFromFile;
    }

    //(void)fread(bytes, fileSizeInBytes, 1, fileStream);

    fclose(fileStream);

    return bytes;
}

GCC -v 的输出为:

gcc main.c fileRW.c -c -std=c11 -O0 -pedantic -Wall -Wextra -Wdiscarded-qualifiers -Wwrite-strings -ggdb3 -v  -D _ENABLE_DEBUG_CODE_=0
Using built-in specs.
COLLECT_GCC=gcc
Target: x86_64-pc-cygwin
Configured with: /mnt/share/cygpkgs/gcc/gcc.x86_64/src/gcc-11.2.0/configure --srcdir=/mnt/share/cygpkgs/gcc/gcc.x86_64/src/gcc-11.2.0 --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --docdir=/usr/share/doc/gcc --htmldir=/usr/share/doc/gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-pc-cygwin --without-libiconv-prefix --without-libintl-prefix --libexecdir=/usr/lib --with-gcc-major-version-only --enable-shared --enable-shared-libgcc --enable-static --enable-version-specific-runtime-libs --enable-bootstrap --enable-__cxa_atexit --with-dwarf2 --with-tune=generic --disable-bootstrap --enable-languages=c,c++,fortran,lto,objc,obj-c++,jit --enable-graphite --enable-threads=posix --enable-libatomic --enable-libgomp --enable-libquadmath --enable-libquadmath-support --disable-libssp --enable-libada --disable-symvers --with-gnu-ld --with-gnu-as --with-cloog-include=/usr/include/cloog-isl --without-libiconv-prefix --without-libintl-prefix --with-system-zlib --enable-linker-build-id --with-default-libstdcxx-abi=gcc4-compatible --enable-libstdcxx-filesystem-ts
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.2.0 (GCC)
COLLECT_GCC_OPTIONS='-c' '-std=c11' '-O0' '-Wpedantic' '-Wall' '-Wextra' '-Wdiscarded-qualifiers' '-Wwrite-strings' '-ggdb3' '-v' '-D' '_ENABLE_DEBUG_CODE_=0' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-pc-cygwin/11/cc1.exe -quiet -v -dD -idirafter /usr/lib/gcc/x86_64-pc-cygwin/11/../../../../lib/../include/w32api -idirafter /usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/lib/../lib/../../include/w32api -D _ENABLE_DEBUG_CODE_=0 main.c -quiet -dumpbase main.c -dumpbase-ext .c -mtune=generic -march=x86-64 -ggdb3 -O0 -Wpedantic -Wall -Wextra -Wdiscarded-qualifiers -Wwrite-strings -std=c11 -version -o /cygdrive/c/Users/Daniel/AppData/Local/Temp/ccIWuk1n.s
GNU C11 (GCC) version 11.2.0 (x86_64-pc-cygwin)
        compiled by GNU C version 11.2.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.24-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-pc-cygwin/11/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/include"
ignoring duplicate directory "/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/lib/../lib/../../include/w32api"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-pc-cygwin/11/include
 /usr/include
 /usr/lib/gcc/x86_64-pc-cygwin/11/../../../../lib/../include/w32api
End of search list.
GNU C11 (GCC) version 11.2.0 (x86_64-pc-cygwin)
        compiled by GNU C version 11.2.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.24-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: befd73bee7080c4d72157ef5c760fe84
COLLECT_GCC_OPTIONS='-c' '-std=c11' '-O0' '-Wpedantic' '-Wall' '-Wextra' '-Wdiscarded-qualifiers' '-Wwrite-strings' '-ggdb3' '-v' '-D' '_ENABLE_DEBUG_CODE_=0' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/as.exe -v --gdwarf-5 -o main.o /cygdrive/c/Users/Daniel/AppData/Local/Temp/ccIWuk1n.s
GNU assembler version 2.37 (x86_64-pc-cygwin) using BFD version (GNU Binutils) 2.37
COLLECT_GCC_OPTIONS='-c' '-std=c11' '-O0' '-Wpedantic' '-Wall' '-Wextra' '-Wdiscarded-qualifiers' '-Wwrite-strings' '-ggdb3' '-v' '-D' '_ENABLE_DEBUG_CODE_=0' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-pc-cygwin/11/cc1.exe -quiet -v -dD -idirafter /usr/lib/gcc/x86_64-pc-cygwin/11/../../../../lib/../include/w32api -idirafter /usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/lib/../lib/../../include/w32api -D _ENABLE_DEBUG_CODE_=0 fileRW.c -quiet -dumpbase fileRW.c -dumpbase-ext .c -mtune=generic -march=x86-64 -ggdb3 -O0 -Wpedantic -Wall -Wextra -Wdiscarded-qualifiers -Wwrite-strings -std=c11 -version -o /cygdrive/c/Users/Daniel/AppData/Local/Temp/ccIWuk1n.s
GNU C11 (GCC) version 11.2.0 (x86_64-pc-cygwin)
        compiled by GNU C version 11.2.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.24-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-pc-cygwin/11/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/include"
ignoring duplicate directory "/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/lib/../lib/../../include/w32api"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-pc-cygwin/11/include
 /usr/include
 /usr/lib/gcc/x86_64-pc-cygwin/11/../../../../lib/../include/w32api
End of search list.
GNU C11 (GCC) version 11.2.0 (x86_64-pc-cygwin)
        compiled by GNU C version 11.2.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.24-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: befd73bee7080c4d72157ef5c760fe84
COLLECT_GCC_OPTIONS='-c' '-std=c11' '-O0' '-Wpedantic' '-Wall' '-Wextra' '-Wdiscarded-qualifiers' '-Wwrite-strings' '-ggdb3' '-v' '-D' '_ENABLE_DEBUG_CODE_=0' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/as.exe -v --gdwarf-5 -o fileRW.o /cygdrive/c/Users/Daniel/AppData/Local/Temp/ccIWuk1n.s
GNU assembler version 2.37 (x86_64-pc-cygwin) using BFD version (GNU Binutils) 2.37
COMPILER_PATH=/usr/lib/gcc/x86_64-pc-cygwin/11/:/usr/lib/gcc/x86_64-pc-cygwin/11/:/usr/lib/gcc/x86_64-pc-cygwin/:/usr/lib/gcc/x86_64-pc-cygwin/11/:/usr/lib/gcc/x86_64-pc-cygwin/:/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/
LIBRARY_PATH=/usr/lib/gcc/x86_64-pc-cygwin/11/:/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/lib/../lib/:/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../lib/:/lib/../lib/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/lib/:/usr/lib/gcc/x86_64-pc-cygwin/11/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-c' '-std=c11' '-O0' '-Wpedantic' '-Wall' '-Wextra' '-Wdiscarded-qualifiers' '-Wwrite-strings' '-ggdb3' '-v' '-D' '_ENABLE_DEBUG_CODE_=0' '-mtune=generic' '-march=x86-64'
windres icon.rs icon.o
gcc main.o fileRW.o icon.o -o code.exe

到目前为止,代码才刚刚开始“形式化”,所以它仍然非常简单。我在一个代码中粉碎了所有软件单元(只是为了这个问题),但现在它至少在 5 个文件中。

而且,我忘了提到我正在使用 Cygwin。我应该更改为 MinGW 吗?


更新3:

大家好,我已经遵循了您的一些建议,我想我已经找到了根本原因。

所以我发现,当二进制文件以 0x00 开头时,这会导致一些问题,阻止我在分配的内存中写入。例如,如果我打开一个以 0x1(或>0x0)开头的二进制文件,则代码似乎工作正常。例如,我尝试读取一个可执行文件,并且在很短的时间内似乎工作正常,但是一旦找到0x0它就会崩溃并且不再写入内存:/

我正在实施的解决方法(暂时)是避免将 0 写在一起。像这样的东西:

...
while( (byteFromFile = fgetc(fileStream)) != EOF )
    {
        if(byteFromFile != '\0') bytes[counter++] = (char)byteFromFile;
    }
...

这当然会引起一个问题,因为我将无法在我的二进制文件中写入 0(或读取它们),这不是很现实。


更新4:

大家好,“根本原因”是GBD。如果我按照上一个答案的说明进行操作(我不能@我的朋友),那么“问题”就不再是“问题”了。GDB with the example shown in the answers.

无论如何,感谢大家抽出时间接受采访。我怎样才能关闭这张票,哈哈?

c gcc malloc 堆内存 calloc

评论

0赞 QuentinUK 11/6/2021
printf 只能打印 4095 个字符
0赞 sj95126 11/6/2021
这可能不是问题,但您不需要调用 ,并且该指针的值在文件关闭后是不确定的,因此不应使用它。free()fileStream
0赞 digits 11/6/2021
我尝试了这个问题中的代码,但没有用:stackoverflow.com/questions/4823177/......我开始认为它可能是编译器。
0赞 David Grayson 11/6/2021
的输出是什么?如果您发布一个最小的可重现示例,以及预期的输出和实际输出,我可以尝试使用 MSYS2 的 GCC 10.3.0 运行它,目标是 .如果可以,请尝试删除文件 I/O,这样我们就不需要外部文件来重现问题。而且我们绝对不需要 MCVE 中的 typedef。gcc -vx86_64-w64-mingw32
0赞 kiner_shah 11/6/2021
GetFileSizeInBytes这是如何定义的?请发布整个代码。此外,请包括必要的示例输入及其预期输出。

答:

1赞 chux - Reinstate Monica 11/6/2021 #1

有什么想法或我做错了什么?:(

257

getc()返回 Range 和 中的 257 个不同值。保存 a 会丢失信息,并可能导致有效的输入字节被误解为信号(有符号时)或永远不会为真(无符号时)。unsigned charEOFcharEOFcharbyteFromFile == EOFchar

// char byteFromFile = 0;
int byteFromFile = 0;
...
  // byteFromFile = (char)getc(fileStream);
  byteFromFile = getc(fileStream);
  if (byteFromFile == EOF) break;

循环的早期退出解释说,“代码适用于大约 1-5 KB 的文件,但大于此大小的文件不起作用。

大小

确保文件大小不超过数学值。fileSizeInBytes + 1size_t

//add
if (fileSizeInBytes < 0 || fileSizeInBytes >= SIZE_MAX) {
  fprintf(stderr, "File size trouble %lld\n", (long long) fileSizeInBytes);
  exit (-1);
}

一次就够了

重复失败的内存分配没有那么有用。如果第一次失败,以后不太可能工作。

// do {
//    bytes = (char*)calloc(fileSizeInBytes + 1, sizeof(char));
// } while (bytes == NULL);

bytes = (char*)calloc(fileSizeInBytes + 1, sizeof(char));
if (bytes == NULL) {
  fprintf(stderr, "Out of memory\n");
  exit (-1);
} 

次要:不需要强制转换,大小到引用对象

// bytes = (char*)calloc(fileSizeInBytes + 1, sizeof(char));
bytes = calloc(fileSizeInBytes + 1, sizeof *bytes);

避免混合类型

为什么代码类型不同?
(就目前而言,在 OP 的情况下看起来还可以)。
fileSizeInBytesifszlong

const fSz fileSizeInBytes = GetFileSizeInBytes(fileName);
...
//for(long i = 0; i < fileSizeInBytes; i++)
for(fSz i = 0; i < fileSizeInBytes; i++)

缺少错误检查

检查以确保文件打开成功。

FILE* fileStream = (FILE*)fopen(fileName, "rb");
// add
if (fileStream == NULL) {
  fprintf(stderr, "Fail to open <%s>\n", fileName);
  exit (-1);
} 

缺少 null 字符

(使用时。
当然应该返回指向字符串的指针。到目前为止,它并不缺少某个 null 字符
malloc()ReadBytesFromFile()bytes[]

char* ReadBytesFromFile(string fileName[]) {
  ...
  bytes[counter] = '\0'; // add
  return bytes;
}

演员表

OP 代码在各个地方使用强制转换。IMO,所有这些都是不必要的或可能隐藏的问题。它们都需要移除或返工。()

也许其他一些问题也正在发生

评论

0赞 digits 11/7/2021
感谢您的回复!我尝试了你说的,但仍然不起作用。我想我已经将问题缩小到了这一点:当二进制文件以“null char”或0x0开头时,代码会崩溃。测试文件(代码在我的原始问题中)以 0 开头,但如果我将其修改为以 0x1 开头,则代码似乎工作正常。我需要做更多的测试,但带有“空字节”的东西似乎是问题所在。
0赞 chux - Reinstate Monica 11/7/2021
@digits 发布一个最小的可重现示例,该示例首先创建二进制文件以供以后读取。
1赞 Nate Eldredge 11/7/2021 #2

所以我发现,当二进制文件以 0x00 开头时,这会导致一些问题,阻止我在分配的内存中写入。例如,如果我打开一个以 0x1(或>0x0)开头的二进制文件,则代码似乎工作正常。例如,我尝试读取一个可执行文件,并且在很短的时间内似乎工作正常,但是一旦它找到一个0x0它就会崩溃并且不再写入内存:/

事实上,您的代码写入内存就好了。但是,您正在 GDB 中检查它。由于 has type ,GDB 的默认设置是将其打印为字符串,这意味着它只打印字符,直到遇到空字节。如果第一个字节为 null,则 GDB 将其打印为空字符串,并忽略之后的任何内容。print bytesbyteschar *

若要指定要转储的字节数,可以改用该命令。例如,将转储十六进制的前 17 个字节;然后,您应该会看到文件中的实际字节数。如果您想将它们视为可打印字符,您可以执行 .使用以获取更多信息。xx/17xb bytesx/17cbhelp x

(作为一个单独的问题,如果其他进程在调用后应该向文件写入更多字节,那么循环将读取过多的字符并溢出缓冲区。您应该跟踪实际存储了多少字节,并在缓冲区已满时退出或扩大缓冲区。statfgetc

评论

0赞 digits 11/7/2021
是的,我认为这就是问题所在:GDB。非常感谢,我快疯了,因为我无法理解根本原因是什么,哈哈