使用 malloc 时出错
Error using malloc
我将 char ** input
从 main()
传递给 processInExp()
函数,然后我将它再次从 processInExp()
函数传递给 getInput()
函数以动态分配它在阅读文件时。
内部 getInput()
函数 input
在检查时已正确分配内存,但在 in processInExp()
中使用它时遇到运行时错误。可能是什么问题?
下面是我的代码:
int getInput(char ** input, const char * fileName)
{
int numInput = 0;
int i, j;
char c;
char tempInput[100];
FILE * pFile;
if((pFile = fopen(fileName, "r")) == NULL)
{
printf("Cannot read file %s\n", fileName);
system("PAUSE");
exit(1);
}
while(!feof(pFile))
{
c = fgetc(pFile);
if(c == '\n') ++numInput;
}
/* printf("%d\n", numInput); */
input = (char**)malloc(numInput * sizeof(char*)); /* #2 MALLOC input */
rewind(pFile);
for(i = 0; !feof(pFile); ++i)
{
fscanf(pFile, "%[^\n]%*c", tempInput);
/* printf("%s\n", tempInput); */
input[i] = (char*)malloc((strlen(tempInput) + 1) * sizeof(char)); /* #3 MALLOC input[] */
strcpy(input[i], tempInput);
/* printf("%s\n", input[i]); */ /* #4 PRINT OUT PERFECTLY */
memset(tempInput, 0, sizeof(tempInput));
}
fclose(pFile);
return numInput;
}
void processInExp(char ** input, char ** output, const char * fileName)
{
int numFormula;
int i;
numFormula = getInput(input, fileName); /* #1 PASSING input */
/* printf("%s\n", input[0]); */ /* #5 RUNTIME ERROR */
output = (char**)malloc(numFormula * sizeof(char*));
system("PAUSE");
for(i = 0; i < numFormula; ++i)
{
convertIntoPost(input[i], output[i]);
printf("%d. %s -> %s", (i + 1), input[i], output[i]);
}
}
C
使用按值传递函数参数传递。因此,从函数 getInput()
内部,您不能更改变量 input
并期望该更改反映在传递给函数的实际参数中。为此,你需要传递一个指向变量的指针,就像在这种情况下,你需要做
int getInput(char *** input, const char * fileName) { //notice the extra *
需要这样称呼它
char ** inp = NULL;
getInput(&inp, ..........);
然后,getInput()
将能够在函数内部分配内存给*input
,这将反映到inp
。
否则,在从 getInput()
中 return 之后,实际参数仍将未初始化并进一步使用它(在您的情况下,在 [=20 中的 for
循环中=] 函数) 将导致 undefined behaviour.
也就是说,还有两件重要的事情要注意,
- 请see why not to cast
malloc()
和家人C
的return值。
- 勾选Why is
while ( !feof (file) )
always wrong?
The C language is pass-by-value without exception.
一个函数不能改变实际参数的值。
正如Sourav所提到的,C使用传值方式进行参数传递,因此processInExp
范围内的输入变量的值是之前在main中分配的内存地址。
这会在您打印 input[0]
时导致分段错误。这是因为 printf
正在尝试打印位于相对于先前分配的内存的地址的字符串,而不是分配给您将字符串复制到的 getInput
函数中的输入的内存。
一个解决方案是传递一个指向输入的指针,因此您的函数签名如下所示:int getInput(char *** input, const char * fileName)
。然后,您需要将对 input
的任何引用更改为 *input
以取消对指针的引用,并将 input
的指针传递给 getInput
,如下所示:getInput(&input, fileName)
.
虽然其他人指出了按值传递的问题,但还有另一个问题可能会发生学习。无需预读文件以确定字符数或行数,然后倒回文件以读取每一行。
看看 getline
,其中 return 读取的字符数 。您需要做的就是保留一个 sum
变量,并在读取所有行后,只需 return (或更新您作为参数提供的指针)即可完成。当然,您可以通过在阅读该行后调用 strlen
来对 fscanf
或 fgets
执行相同的操作。
下面是一个简短的例子,在确定字符数(没有 newline
)的同时读取一个文本文件并return将该信息传递给调用函数。正如您需要将指针传递给 getInput
中的指针数组一样,我们将使用指针作为参数传递给 return,将 line
和 character
计数传递给我们的调用函数.如果声明并调用读取文件的函数如下:
size_t nline = 0; /* placeholders to be filled by readtxtfile */
size_t nchar = 0; /* containing number of lines/chars in file */
...
char **file = readtxtfile (fn, &nline, &nchar);
通过在调用函数中声明变量,然后将指向变量的指针作为参数传递(使用 urnary &
),您可以更新函数中的值并让这些值可供使用在 main
中(或您从中调用 readtxtfile
的任何函数。)
说明这些要点的一个简单示例可以是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NMAX 256
char **readtxtfile (char *fn, size_t *idx, size_t *sum);
void prn_chararray (char **ca);
void free_chararray (char **ca);
int main (int argc, char **argv) {
size_t nline = 0; /* placeholders to be filled by readtxtfile */
size_t nchar = 0; /* containing number of lines/chars in file */
char *fn = argc > 1 ? argv[1] : NULL;/* if fn not given, read stdin */
/* read each file into an array of strings,
* number of lines/chars read updated in nline, nchar
*/
char **file = readtxtfile (fn, &nline, &nchar);
/* output number of lines read & chars read and from where */
printf ("\n read '%zu' lines & '%zu' chars from file: %s\n\n",
nline, nchar, fn ? fn : "stdin");
/* simple print function to print all lines */
if (file) prn_chararray (file);
/* simple free memory function */
if (file) free_chararray (file);
return 0;
}
/* simple function using getline to read any text file and return
* the lines read in an array of pointers. user is responsible for
* freeing memory when no longer needed
*/
char **readtxtfile (char *fn, size_t *idx, size_t *sum)
{
char *ln = NULL; /* NULL forces getline to allocate */
size_t n = 0; /* line buf size (0 - use default) */
ssize_t nchr = 0; /* number of chars actually read */
size_t nmax = NMAX; /* check for reallocation */
char **array = NULL; /* array to hold lines read */
FILE *fp = NULL; /* file pointer to open file fn */
/* open / validate file or read stdin */
fp = fn ? fopen (fn, "r") : stdin;
if (!fp) {
fprintf (stderr, "%s() error: file open failed '%s'.", __func__, fn);
return NULL;
}
/* allocate NMAX pointers to char* */
if (!(array = calloc (NMAX, sizeof *array))) {
fprintf (stderr, "%s() error: memory allocation failed.", __func__);
return NULL;
}
/* read each line from stdin - dynamicallly allocated */
while ((nchr = getline (&ln, &n, fp)) != -1)
{
/* strip newline or carriage rtn */
while (nchr > 0 && (ln[nchr-1] == '\n' || ln[nchr-1] == '\r'))
ln[--nchr] = 0;
*sum += nchr; /* add chars in line to sum */
array[*idx] = strdup (ln); /* allocate/copy ln to array */
(*idx)++; /* increment value at index */
if (*idx == nmax) { /* if lines exceed nmax, reallocate */
char **tmp = realloc (array, nmax * 2);
if (!tmp) {
fprintf (stderr, "%s() error: reallocation failed.\n", __func__);
exit (EXIT_FAILURE); /* or return NULL; */
}
array = tmp;
nmax *= 2;
}
}
if (ln) free (ln); /* free memory allocated by getline */
if (fp != stdin) fclose (fp); /* close open file descriptor */
return array;
}
/* print an array of character pointers. */
void prn_chararray (char **ca)
{
register size_t n = 0;
while (ca[n])
{
printf (" arr[%3zu] %s\n", n, ca[n]);
n++;
}
}
/* free array of char* */
void free_chararray (char **ca)
{
if (!ca) return;
register size_t n = 0;
while (ca[n])
free (ca[n++]);
free (ca);
}
Use/Output
$ ./bin/getline_ccount <dat/fc-list-fonts.txt
read '187' lines & '7476' chars from file: stdin
arr[ 0] andalemo.ttf: Andale Mono - Regular
arr[ 1] arialbd.ttf: Arial - Bold
arr[ 2] arialbi.ttf: Arial - Bold Italic
arr[ 3] ariali.ttf: Arial - Italic
arr[ 4] arialnbi.ttf: Arial
arr[ 5] arialnb.ttf: Arial
arr[ 6] arialni.ttf: Arial
arr[ 7] arialn.ttf: Arial
arr[ 8] arial.ttf: Arial - Regular
arr[ 9] ARIALUNI.TTF: Arial Unicode MS - Regular
arr[ 10] ariblk.ttf: Arial
arr[ 11] Bailey Script Regular.ttf: Bailey Script - Regular
arr[ 12] Bailey_Script_Regular.ttf: Bailey Script - Regular
arr[ 13] Belwe Gotisch.ttf: Belwe Gotisch - Regular
arr[ 14] Belwe_Gotisch.ttf: Belwe Gotisch - Regular
<snip>
Memory/Leak检查
每当您 allocated/free 代码中的内存时,不要忘记使用内存检查器来确保您的代码中没有内存错误或泄漏:
$ valgrind ./bin/getline_ccount <dat/fc-list-fonts.txt
==20259== Memcheck, a memory error detector
==20259== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==20259== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==20259== Command: ./bin/getline_readfile_function
==20259==
read '187' line from file: stdin
arr[ 0] andalemo.ttf: Andale Mono - Regular
arr[ 1] arialbd.ttf: Arial - Bold
arr[ 2] arialbi.ttf: Arial - Bold Italic
arr[ 3] ariali.ttf: Arial - Italic
<snip>
==20259==
==20259== HEAP SUMMARY:
==20259== in use at exit: 0 bytes in 0 blocks
==20259== total heap usage: 189 allocs, 189 frees, 9,831 bytes allocated
==20259==
==20259== All heap blocks were freed -- no leaks are possible
==20259==
==20259== For counts of detected and suppressed errors, rerun with: -v
==20259== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
根据评论进行跟进
您在评论中发布的代码存在几个问题:
for(i = 0; !feof(pFile); ++i) {
fscanf(pFile, "%[^\n]%*c", tempInput);
/* printf("%s\n", tempInput); */
input[i] = (char*)malloc((strlen(tempInput) + 1) * sizeof(char));
strcpy(input[i], tempInput);
printf("%s\n", input[i]);
memset(tempInput, 0, sizeof(tempInput));
}
for(i = 0; i < numInput; ++i) {
convertIntoPost(input[i], output[i]);
}
首先,阅读第一条评论中的 link,了解为什么 feof
在循环中使用它来指示 EOF 时会导致问题。其次,函数具有 return values
,将它们用于优势的能力告诉您是否使用了正确的函数来完成工作。
你在尝试用 fscanf
阅读整行时遇到的困难应该告诉你一些事情......你通过选择格式说明符 "%[^\n]%*c"
来解决的问题阅读包含 whitespace
的行是 fscanf
不适合该工作的确切原因。
为什么? scanf
系列函数是为读取 离散 值而创建的。他们的 return
基于:
the number of input items successfully matched and assigned
使用您的格式说明符,成功读取的项目数为 1
。 *%c
读取并丢弃 newline
,但未添加到项目计数中。这在尝试读取可能包含空行的文件时会导致大问题。那么会发生什么?您遇到 input failure
和 fscanf
returns 0
-- 但它仍然是一个非常有效的行。发生这种情况时,不会读取任何内容。您不能将 return 检查为 >= 0
,因为当您遇到空行时,您将永远循环...
使用您的格式说明符,您也无法检查 EOF
。为什么?使用 scanf
函数族:
The value EOF
is returned if the end of input
is reached before
either the first successful conversion
or a matching failure
occurs.
你的情况永远不会发生这种情况,因为你有一个 input failure
和 fscanf
(不是 end of input
)并且没有发生 matching failure
。您是否开始明白为什么 fscanf
可能不是完成这项工作的正确工具?
C 库为 line-oriented
输入提供了两个函数。它们是 fgets
和 getline
。两者都将整行文本读入行缓冲区。这将包括每行末尾的 newline
(包括空行)。因此,当您使用其中任何一个来阅读文本时,最好通过覆盖 null-terminating
字符来删除 newline
。
用哪个?使用 fgets
,您可以通过适当调整字符缓冲区的大小来限制读取的字符数。 getline
现在是 C 库的一部分,它提供了 returning 实际读取的字符数的额外好处(奖励),但无论多长它都会读取该行,因为它为您动态分配缓冲区。我更喜欢它,但只知道你需要检查它已读取的字符数。
因为我在上面提供了一个 getline
示例,所以您的读取循环可以用 fgets
编写得更好,如下所示:
while (fgets (tempInput, MAXL, pFile) != NULL) {
nchr = strlen (tempInput);
while (nchr && (tempInput[nchr-1] == '\n' || tempInput[nchr-1] == '\r'))
tempInput[--nchr] = 0; /* strip newlines & carriage returns */
input[i++] = strdup (tempInput); /* allocates & copies tempInput */
}
numInput = i;
接下来,您的分配不需要转换为(char *)
。 malloc
和 calloc
的 return 只是指向分配的内存块(即地址)的指针。 (不管你给什么分配内存都是一样的)不需要sizeof (char)
。它总是 1
。所以只写:
input[i] = malloc (strlen(tempInput) + 1);
strcpy (input[i], tempInput);
allocate
和 copy
更方便的方法是使用 strdup
。使用strdup
,上面的两行就变成了:
input[i++] = strdup (tempInput); /* allocates & copies */
接下来就不用memset
了。
memset(tempInput, 0, sizeof(tempInput));
如果 tempInput
被声明为保存 100 个字符,例如:tempInput[100]
,您可以一遍又一遍地将最多 99 char
的字符串读入同一个缓冲区,而无需将内存归零。为什么?刺是 null-terminated
。您不关心 null-terminator
...
之后缓冲区中的内容
要理解的内容很多。将所有内容放在一个简短的示例中,您可以执行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXL 256
/* dummy function */
void convertIntoPost (char *in, char **out)
{
size_t i = 0, len = strlen (in);
*out = calloc (1, len + 1);
for (i = 0; i < len; i++) {
(*out)[len-i-1] = in[i];
}
}
int main (int argc, char **argv) {
char tempInput[MAXL] = {0};
char **input = NULL, **output = NULL;
size_t i = 0, numInput = 0;
size_t nchr = 0;
FILE *pFile = NULL;
pFile = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!pFile) {
fprintf (stderr, "error: file open failed '%s'.\n",
argv[1] ? argv[1] : "stdin");
return 1;
}
input = calloc (1, MAXL); /* allocate MAXL pointer for input & output */
output = calloc (1, MAXL); /* calloc allocates and sets memory to 0-NULL */
if (!input || !output) { /* validate allocation */
fprintf (stderr, "error: memory allocation failed.\n");
return 1;
}
while (fgets (tempInput, MAXL, pFile) != NULL) {
nchr = strlen (tempInput);
while (nchr && (tempInput[nchr-1] == '\n' || tempInput[nchr-1] == '\r'))
tempInput[--nchr] = 0;
input[i++] = strdup (tempInput); /* allocates & copies */
}
numInput = i;
fclose (pFile);
/* call convertIntoPost with input[i] and &output[i] */
for (i = 0; i < numInput; ++i) {
convertIntoPost (input[i], &output[i]);
printf (" input[%2zu]: %-25s output[%2zu]: %s\n",
i, input[i], i, output[i]);
}
/* free all memory */
for (i = 0; i < numInput; ++i) {
free (input[i]), free (output[i]);
}
free (input), free (output);
return 0;
}
示例输出
$ ./bin/feoffix ../dat/captnjack.txt
input[ 0]: This is a tale output[ 0]: elat a si sihT
input[ 1]: Of Captain Jack Sparrow output[ 1]: worrapS kcaJ niatpaC fO
input[ 2]: A Pirate So Brave output[ 2]: evarB oS etariP A
input[ 3]: On the Seven Seas. output[ 3]: .saeS neveS eht nO
编译代码的注意事项
始终 在启用 警告 的情况下编译您的代码。这样编译器可以帮助指出您的代码可能有歧义的地方,等等。要在编译时启用警告,只需将 -Wall
和 -Wextra
添加到您的编译字符串。 (如果您真的想要所有警告,请添加 -pedantic
(定义:过度关注琐碎的细节))。花时间阅读并理解编译器通过警告告诉您的内容(它们真的非常好,您将很快了解每个警告的含义)。然后......去解决问题,这样你的代码编译 没有 任何警告。
只有非常罕见和有限的情况允许'understand and choose to allow'保留警告(例如在使用您无法访问的图书馆时源代码)
所以综上所述,当你编译你的代码时,至少你应该编译以下内容用于测试和开发:
gcc -Wall -Wextra -o progname progname.c -g
对于 gcc
,-g
选项告诉编译器生成额外的调试信息以供调试器使用 gdb
(学习)。
当你解决了所有的错误并准备好最终编译你的代码时,你会想要添加优化,比如优化级别 -On
(这是资本 O
[不是zero] 其中 'n'
是级别 1, 2, or 3
(0
是默认值),-Ofast
本质上是 -O3
并进行了一些额外的优化)。您可能还想考虑告诉编译器 inline
尽可能使用 -finline-functions
来消除函数调用开销。所以对于最终编译你会想要类似的东西:
gcc -Wall -Wextra -finline-functions -Ofast -o progname progname.c
优化可以使性能提高 10 倍并缩短程序执行时间(在某些情况下性能提高 1000%(300-500% 的改进很常见))。非常值得添加几个开关。
我将 char ** input
从 main()
传递给 processInExp()
函数,然后我将它再次从 processInExp()
函数传递给 getInput()
函数以动态分配它在阅读文件时。
内部 getInput()
函数 input
在检查时已正确分配内存,但在 in processInExp()
中使用它时遇到运行时错误。可能是什么问题?
下面是我的代码:
int getInput(char ** input, const char * fileName)
{
int numInput = 0;
int i, j;
char c;
char tempInput[100];
FILE * pFile;
if((pFile = fopen(fileName, "r")) == NULL)
{
printf("Cannot read file %s\n", fileName);
system("PAUSE");
exit(1);
}
while(!feof(pFile))
{
c = fgetc(pFile);
if(c == '\n') ++numInput;
}
/* printf("%d\n", numInput); */
input = (char**)malloc(numInput * sizeof(char*)); /* #2 MALLOC input */
rewind(pFile);
for(i = 0; !feof(pFile); ++i)
{
fscanf(pFile, "%[^\n]%*c", tempInput);
/* printf("%s\n", tempInput); */
input[i] = (char*)malloc((strlen(tempInput) + 1) * sizeof(char)); /* #3 MALLOC input[] */
strcpy(input[i], tempInput);
/* printf("%s\n", input[i]); */ /* #4 PRINT OUT PERFECTLY */
memset(tempInput, 0, sizeof(tempInput));
}
fclose(pFile);
return numInput;
}
void processInExp(char ** input, char ** output, const char * fileName)
{
int numFormula;
int i;
numFormula = getInput(input, fileName); /* #1 PASSING input */
/* printf("%s\n", input[0]); */ /* #5 RUNTIME ERROR */
output = (char**)malloc(numFormula * sizeof(char*));
system("PAUSE");
for(i = 0; i < numFormula; ++i)
{
convertIntoPost(input[i], output[i]);
printf("%d. %s -> %s", (i + 1), input[i], output[i]);
}
}
C
使用按值传递函数参数传递。因此,从函数 getInput()
内部,您不能更改变量 input
并期望该更改反映在传递给函数的实际参数中。为此,你需要传递一个指向变量的指针,就像在这种情况下,你需要做
int getInput(char *** input, const char * fileName) { //notice the extra *
需要这样称呼它
char ** inp = NULL;
getInput(&inp, ..........);
然后,getInput()
将能够在函数内部分配内存给*input
,这将反映到inp
。
否则,在从 getInput()
中 return 之后,实际参数仍将未初始化并进一步使用它(在您的情况下,在 [=20 中的 for
循环中=] 函数) 将导致 undefined behaviour.
也就是说,还有两件重要的事情要注意,
- 请see why not to cast
malloc()
和家人C
的return值。 - 勾选Why is
while ( !feof (file) )
always wrong?
The C language is pass-by-value without exception.
一个函数不能改变实际参数的值。
正如Sourav所提到的,C使用传值方式进行参数传递,因此processInExp
范围内的输入变量的值是之前在main中分配的内存地址。
这会在您打印 input[0]
时导致分段错误。这是因为 printf
正在尝试打印位于相对于先前分配的内存的地址的字符串,而不是分配给您将字符串复制到的 getInput
函数中的输入的内存。
一个解决方案是传递一个指向输入的指针,因此您的函数签名如下所示:int getInput(char *** input, const char * fileName)
。然后,您需要将对 input
的任何引用更改为 *input
以取消对指针的引用,并将 input
的指针传递给 getInput
,如下所示:getInput(&input, fileName)
.
虽然其他人指出了按值传递的问题,但还有另一个问题可能会发生学习。无需预读文件以确定字符数或行数,然后倒回文件以读取每一行。
看看 getline
,其中 return 读取的字符数 。您需要做的就是保留一个 sum
变量,并在读取所有行后,只需 return (或更新您作为参数提供的指针)即可完成。当然,您可以通过在阅读该行后调用 strlen
来对 fscanf
或 fgets
执行相同的操作。
下面是一个简短的例子,在确定字符数(没有 newline
)的同时读取一个文本文件并return将该信息传递给调用函数。正如您需要将指针传递给 getInput
中的指针数组一样,我们将使用指针作为参数传递给 return,将 line
和 character
计数传递给我们的调用函数.如果声明并调用读取文件的函数如下:
size_t nline = 0; /* placeholders to be filled by readtxtfile */
size_t nchar = 0; /* containing number of lines/chars in file */
...
char **file = readtxtfile (fn, &nline, &nchar);
通过在调用函数中声明变量,然后将指向变量的指针作为参数传递(使用 urnary &
),您可以更新函数中的值并让这些值可供使用在 main
中(或您从中调用 readtxtfile
的任何函数。)
说明这些要点的一个简单示例可以是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NMAX 256
char **readtxtfile (char *fn, size_t *idx, size_t *sum);
void prn_chararray (char **ca);
void free_chararray (char **ca);
int main (int argc, char **argv) {
size_t nline = 0; /* placeholders to be filled by readtxtfile */
size_t nchar = 0; /* containing number of lines/chars in file */
char *fn = argc > 1 ? argv[1] : NULL;/* if fn not given, read stdin */
/* read each file into an array of strings,
* number of lines/chars read updated in nline, nchar
*/
char **file = readtxtfile (fn, &nline, &nchar);
/* output number of lines read & chars read and from where */
printf ("\n read '%zu' lines & '%zu' chars from file: %s\n\n",
nline, nchar, fn ? fn : "stdin");
/* simple print function to print all lines */
if (file) prn_chararray (file);
/* simple free memory function */
if (file) free_chararray (file);
return 0;
}
/* simple function using getline to read any text file and return
* the lines read in an array of pointers. user is responsible for
* freeing memory when no longer needed
*/
char **readtxtfile (char *fn, size_t *idx, size_t *sum)
{
char *ln = NULL; /* NULL forces getline to allocate */
size_t n = 0; /* line buf size (0 - use default) */
ssize_t nchr = 0; /* number of chars actually read */
size_t nmax = NMAX; /* check for reallocation */
char **array = NULL; /* array to hold lines read */
FILE *fp = NULL; /* file pointer to open file fn */
/* open / validate file or read stdin */
fp = fn ? fopen (fn, "r") : stdin;
if (!fp) {
fprintf (stderr, "%s() error: file open failed '%s'.", __func__, fn);
return NULL;
}
/* allocate NMAX pointers to char* */
if (!(array = calloc (NMAX, sizeof *array))) {
fprintf (stderr, "%s() error: memory allocation failed.", __func__);
return NULL;
}
/* read each line from stdin - dynamicallly allocated */
while ((nchr = getline (&ln, &n, fp)) != -1)
{
/* strip newline or carriage rtn */
while (nchr > 0 && (ln[nchr-1] == '\n' || ln[nchr-1] == '\r'))
ln[--nchr] = 0;
*sum += nchr; /* add chars in line to sum */
array[*idx] = strdup (ln); /* allocate/copy ln to array */
(*idx)++; /* increment value at index */
if (*idx == nmax) { /* if lines exceed nmax, reallocate */
char **tmp = realloc (array, nmax * 2);
if (!tmp) {
fprintf (stderr, "%s() error: reallocation failed.\n", __func__);
exit (EXIT_FAILURE); /* or return NULL; */
}
array = tmp;
nmax *= 2;
}
}
if (ln) free (ln); /* free memory allocated by getline */
if (fp != stdin) fclose (fp); /* close open file descriptor */
return array;
}
/* print an array of character pointers. */
void prn_chararray (char **ca)
{
register size_t n = 0;
while (ca[n])
{
printf (" arr[%3zu] %s\n", n, ca[n]);
n++;
}
}
/* free array of char* */
void free_chararray (char **ca)
{
if (!ca) return;
register size_t n = 0;
while (ca[n])
free (ca[n++]);
free (ca);
}
Use/Output
$ ./bin/getline_ccount <dat/fc-list-fonts.txt
read '187' lines & '7476' chars from file: stdin
arr[ 0] andalemo.ttf: Andale Mono - Regular
arr[ 1] arialbd.ttf: Arial - Bold
arr[ 2] arialbi.ttf: Arial - Bold Italic
arr[ 3] ariali.ttf: Arial - Italic
arr[ 4] arialnbi.ttf: Arial
arr[ 5] arialnb.ttf: Arial
arr[ 6] arialni.ttf: Arial
arr[ 7] arialn.ttf: Arial
arr[ 8] arial.ttf: Arial - Regular
arr[ 9] ARIALUNI.TTF: Arial Unicode MS - Regular
arr[ 10] ariblk.ttf: Arial
arr[ 11] Bailey Script Regular.ttf: Bailey Script - Regular
arr[ 12] Bailey_Script_Regular.ttf: Bailey Script - Regular
arr[ 13] Belwe Gotisch.ttf: Belwe Gotisch - Regular
arr[ 14] Belwe_Gotisch.ttf: Belwe Gotisch - Regular
<snip>
Memory/Leak检查
每当您 allocated/free 代码中的内存时,不要忘记使用内存检查器来确保您的代码中没有内存错误或泄漏:
$ valgrind ./bin/getline_ccount <dat/fc-list-fonts.txt
==20259== Memcheck, a memory error detector
==20259== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==20259== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==20259== Command: ./bin/getline_readfile_function
==20259==
read '187' line from file: stdin
arr[ 0] andalemo.ttf: Andale Mono - Regular
arr[ 1] arialbd.ttf: Arial - Bold
arr[ 2] arialbi.ttf: Arial - Bold Italic
arr[ 3] ariali.ttf: Arial - Italic
<snip>
==20259==
==20259== HEAP SUMMARY:
==20259== in use at exit: 0 bytes in 0 blocks
==20259== total heap usage: 189 allocs, 189 frees, 9,831 bytes allocated
==20259==
==20259== All heap blocks were freed -- no leaks are possible
==20259==
==20259== For counts of detected and suppressed errors, rerun with: -v
==20259== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
根据评论进行跟进
您在评论中发布的代码存在几个问题:
for(i = 0; !feof(pFile); ++i) {
fscanf(pFile, "%[^\n]%*c", tempInput);
/* printf("%s\n", tempInput); */
input[i] = (char*)malloc((strlen(tempInput) + 1) * sizeof(char));
strcpy(input[i], tempInput);
printf("%s\n", input[i]);
memset(tempInput, 0, sizeof(tempInput));
}
for(i = 0; i < numInput; ++i) {
convertIntoPost(input[i], output[i]);
}
首先,阅读第一条评论中的 link,了解为什么 feof
在循环中使用它来指示 EOF 时会导致问题。其次,函数具有 return values
,将它们用于优势的能力告诉您是否使用了正确的函数来完成工作。
你在尝试用 fscanf
阅读整行时遇到的困难应该告诉你一些事情......你通过选择格式说明符 "%[^\n]%*c"
来解决的问题阅读包含 whitespace
的行是 fscanf
不适合该工作的确切原因。
为什么? scanf
系列函数是为读取 离散 值而创建的。他们的 return
基于:
the number of input items successfully matched and assigned
使用您的格式说明符,成功读取的项目数为 1
。 *%c
读取并丢弃 newline
,但未添加到项目计数中。这在尝试读取可能包含空行的文件时会导致大问题。那么会发生什么?您遇到 input failure
和 fscanf
returns 0
-- 但它仍然是一个非常有效的行。发生这种情况时,不会读取任何内容。您不能将 return 检查为 >= 0
,因为当您遇到空行时,您将永远循环...
使用您的格式说明符,您也无法检查 EOF
。为什么?使用 scanf
函数族:
The value
EOF
is returned if theend of input
is reached before either thefirst successful conversion
or amatching failure
occurs.
你的情况永远不会发生这种情况,因为你有一个 input failure
和 fscanf
(不是 end of input
)并且没有发生 matching failure
。您是否开始明白为什么 fscanf
可能不是完成这项工作的正确工具?
C 库为 line-oriented
输入提供了两个函数。它们是 fgets
和 getline
。两者都将整行文本读入行缓冲区。这将包括每行末尾的 newline
(包括空行)。因此,当您使用其中任何一个来阅读文本时,最好通过覆盖 null-terminating
字符来删除 newline
。
用哪个?使用 fgets
,您可以通过适当调整字符缓冲区的大小来限制读取的字符数。 getline
现在是 C 库的一部分,它提供了 returning 实际读取的字符数的额外好处(奖励),但无论多长它都会读取该行,因为它为您动态分配缓冲区。我更喜欢它,但只知道你需要检查它已读取的字符数。
因为我在上面提供了一个 getline
示例,所以您的读取循环可以用 fgets
编写得更好,如下所示:
while (fgets (tempInput, MAXL, pFile) != NULL) {
nchr = strlen (tempInput);
while (nchr && (tempInput[nchr-1] == '\n' || tempInput[nchr-1] == '\r'))
tempInput[--nchr] = 0; /* strip newlines & carriage returns */
input[i++] = strdup (tempInput); /* allocates & copies tempInput */
}
numInput = i;
接下来,您的分配不需要转换为(char *)
。 malloc
和 calloc
的 return 只是指向分配的内存块(即地址)的指针。 (不管你给什么分配内存都是一样的)不需要sizeof (char)
。它总是 1
。所以只写:
input[i] = malloc (strlen(tempInput) + 1);
strcpy (input[i], tempInput);
allocate
和 copy
更方便的方法是使用 strdup
。使用strdup
,上面的两行就变成了:
input[i++] = strdup (tempInput); /* allocates & copies */
接下来就不用memset
了。
memset(tempInput, 0, sizeof(tempInput));
如果 tempInput
被声明为保存 100 个字符,例如:tempInput[100]
,您可以一遍又一遍地将最多 99 char
的字符串读入同一个缓冲区,而无需将内存归零。为什么?刺是 null-terminated
。您不关心 null-terminator
...
要理解的内容很多。将所有内容放在一个简短的示例中,您可以执行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXL 256
/* dummy function */
void convertIntoPost (char *in, char **out)
{
size_t i = 0, len = strlen (in);
*out = calloc (1, len + 1);
for (i = 0; i < len; i++) {
(*out)[len-i-1] = in[i];
}
}
int main (int argc, char **argv) {
char tempInput[MAXL] = {0};
char **input = NULL, **output = NULL;
size_t i = 0, numInput = 0;
size_t nchr = 0;
FILE *pFile = NULL;
pFile = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!pFile) {
fprintf (stderr, "error: file open failed '%s'.\n",
argv[1] ? argv[1] : "stdin");
return 1;
}
input = calloc (1, MAXL); /* allocate MAXL pointer for input & output */
output = calloc (1, MAXL); /* calloc allocates and sets memory to 0-NULL */
if (!input || !output) { /* validate allocation */
fprintf (stderr, "error: memory allocation failed.\n");
return 1;
}
while (fgets (tempInput, MAXL, pFile) != NULL) {
nchr = strlen (tempInput);
while (nchr && (tempInput[nchr-1] == '\n' || tempInput[nchr-1] == '\r'))
tempInput[--nchr] = 0;
input[i++] = strdup (tempInput); /* allocates & copies */
}
numInput = i;
fclose (pFile);
/* call convertIntoPost with input[i] and &output[i] */
for (i = 0; i < numInput; ++i) {
convertIntoPost (input[i], &output[i]);
printf (" input[%2zu]: %-25s output[%2zu]: %s\n",
i, input[i], i, output[i]);
}
/* free all memory */
for (i = 0; i < numInput; ++i) {
free (input[i]), free (output[i]);
}
free (input), free (output);
return 0;
}
示例输出
$ ./bin/feoffix ../dat/captnjack.txt
input[ 0]: This is a tale output[ 0]: elat a si sihT
input[ 1]: Of Captain Jack Sparrow output[ 1]: worrapS kcaJ niatpaC fO
input[ 2]: A Pirate So Brave output[ 2]: evarB oS etariP A
input[ 3]: On the Seven Seas. output[ 3]: .saeS neveS eht nO
编译代码的注意事项
始终 在启用 警告 的情况下编译您的代码。这样编译器可以帮助指出您的代码可能有歧义的地方,等等。要在编译时启用警告,只需将 -Wall
和 -Wextra
添加到您的编译字符串。 (如果您真的想要所有警告,请添加 -pedantic
(定义:过度关注琐碎的细节))。花时间阅读并理解编译器通过警告告诉您的内容(它们真的非常好,您将很快了解每个警告的含义)。然后......去解决问题,这样你的代码编译 没有 任何警告。
只有非常罕见和有限的情况允许'understand and choose to allow'保留警告(例如在使用您无法访问的图书馆时源代码)
所以综上所述,当你编译你的代码时,至少你应该编译以下内容用于测试和开发:
gcc -Wall -Wextra -o progname progname.c -g
对于 gcc
,-g
选项告诉编译器生成额外的调试信息以供调试器使用 gdb
(学习)。
当你解决了所有的错误并准备好最终编译你的代码时,你会想要添加优化,比如优化级别 -On
(这是资本 O
[不是zero] 其中 'n'
是级别 1, 2, or 3
(0
是默认值),-Ofast
本质上是 -O3
并进行了一些额外的优化)。您可能还想考虑告诉编译器 inline
尽可能使用 -finline-functions
来消除函数调用开销。所以对于最终编译你会想要类似的东西:
gcc -Wall -Wextra -finline-functions -Ofast -o progname progname.c
优化可以使性能提高 10 倍并缩短程序执行时间(在某些情况下性能提高 1000%(300-500% 的改进很常见))。非常值得添加几个开关。