我只有约2周的C课程介绍,所以我仍然在理解很多这个。我可能不理解技术答案,但我很欣赏任何见解。此外,我们还没有学到很多内容,但是如此先进的技术将会超出我的想象。
我正在编写一个程序,它将从文本文件中读取并将字符存储到双指针"数组" (因为没有更好的术语)。它基本上存储了MxM字搜索拼图。
拼图永远不会超过50x50所以我从malloc第一行开始到50个字符,存储第一行以确定列数,这样我就可以重新分配"行和列的数量"到适当的大小并存储其余的拼图。
在do / while进行第二次迭代后,我遇到了分段错误。我认为我的语法可能是我的前两个malloc命令之一(使用传递的双指针让我感到困惑)。另外,我知道我还没有添加代码来处理失败的malloc。
如果有人能指导我朝着正确的方向前进,我将非常感激。提前致谢。
这是我到目前为止所做的:
#include<stdio.h>
#include <stdlib.h>
#include <ctype.h>
void storePuzzle(char***);
int main()
{
char **arr;
storePuzzle(&arr);
return 0;
}
void storePuzzle(char ***arr)
{
int count, count2, size = 0;
int c;
*arr = (char**)malloc(sizeof(char*));
*arr[0] = (char*)malloc(sizeof(char)*50);
/*determine size of wordsearch and fill first row*/
do
{
c = getchar();
if(isalpha(c))
{
*arr[0][size] = c;
size++;
}
} while(c != '\n');
/*realloc row/columns*/
*arr = (char**)realloc(*arr, sizeof(char*)*size);
for(count = 0; count < size; count++)
{
*arr[count] = (char*)realloc(*arr[count], sizeof(char)*size);
}
}
答案 0 :(得分:1)
好的,这里有一些提示:
char **arr;
storePuzzle(&arr);
return 0;
在上面的代码块中,您没有初始化arr,之后不使用结果。因此,没有理由存在该参数。如果您打算在之后使用该值,则可以让storePuzzle返回指向您的指针。
*arr[0]
[]的优先级高于*。另外* arr和arr [0]基本上做同样的事情,所以当你使用0而不是任何其他数字时这种方法有效。
*arr[count] = (char*)realloc(*arr[count], sizeof(char)*size);
您正在重新分配从未分配过的指针。请记住,您为arr [0]分配了内存,但没有分配其他内存。如果你想采用这种方法,你可以重新分配(* arr)[0],但其他人需要使用malloc:
(*arr)[count] = (char*)malloc(sizeof(char)*size);
答案 1 :(得分:1)
以下将指导一些分配问题,但不是所有OP代码的修复。
分配内存时,请考虑以下模式:
object_pointer = /* no cast needed */ malloc(sizeof *object_pointer * number_needed);
if (object_pointer == NULL) OutOfMemory();
重新分配时:
void_pointer = /* no cast needed */ realloc(object_pointer,
sizeof *object_pointer * new_number_needed);
if (void_pointer == NULL) {
Handle Out of memory, original object_pointer and its size still valid.
maybe exit with error message
}
object_pointer = /* cast not needed */ void_pointer
这对storePuzzle(char ***arr)
有什么影响?
// Avoid magic numbers like 50 littering code
#define PUZZLE_N 50
void storePuzzle(char ***arr) {
*arr = malloc(sizeof *(*arr));
if (*arr == NULL) return;
*arr[0] = malloc(sizeof *(*arr[0]) * PUZZLE_N);
if (*arr[0] == NULL) {
free(*arr);
return;
}
/*determine size of wordsearch and fill first row*/
size_t size = 0;
int c;
do {
c = getchar();
if (isalpha(c)) {
*arr[0][size] = c;
size++;
}
} while(c != '\n' && c != EOF && size < PUZZLE_N);
// Did you want a null character at the end?
*arr[0][size++] = '\0';
/* realloc row/columns */
void *p = realloc(*arr, sizeof *(*arr) * size);
if (p == NULL) Handle_OOM(); // details omitted
*arr = p;
for(size_t count = 0; count < size; count++) {
void *p = realloc(*arr[count], sizeof *(*arr[count]) * size);
if (p == NULL) Handle_OOM(); // details omitted
*arr[count] = p;
}
}
答案 2 :(得分:0)
考虑将您的一些功能整合到更小的模块中,以便在遇到困难时更轻松地进行调试。例如,当你需要创建一个缓冲区数组(即char **数组)时,专用一个函数来做到这一点。例如:
char ** Create2D(int lines, int lengths)
{
int i;
char **arr = NULL;
arr = calloc(lines, sizeof(*arr));//creates lines pointers, one for each row of data.
if(arr)
{
for(i=0;i<lines;i++)
{
arr[i] = calloc(lengths, 1);//creates space for content in each row.
}
}
return arr;
}
请注意,在调用calloc时,返回的内容不是 as this is not recommended in C 。
使用与此处所示相同的方法(并且更深入一级)可以编写一个函数来容纳三重指针char ***array
,尽管在某种程度上可以编写您的程序,这样就不需要了。请参阅这些关于 a three star programmer 的评论。
这个2D函数将被调用如下:
int main(void)
{
int i;
char **arr = Create2D(10, 80);//creates 10 pointers each having room for 80 characters
if(arr)
{
storePuzzle(&arr);//pre-allocated buffer should simplify storePuzzle function
Free2D(arr, 10); //defined below, frees all memory allocated above.
}
return 0;
}
对于每个动态内存分配,都需要有一个相应的免费调用:
void Free2D(char **arr, int lines)
{
int i;
for(i=0;i<lines;i++)
{
free(arr[i]);
}
free(arr);
}
答案 3 :(得分:0)
第一个指针和数组是C中两个完全独立的类型。虽然您可以使用指针访问存储在数组中的值,但您可以使用指向的指针的指针建模2D数组,指向指针的指针本身不是array
。
声明数组,并且仅使用括号声明,例如type name[x];
。此处name
是一个单维数组,x
元素类型为type
。 (例如,对于x
整数,它将是int name[x];
。(复合文字初始化的讨论将留待以后使用)
您还可以将指针定义为包含x
整数的内存块,例如: int *namep = name;
其中整数指针 namep
现在指向name
中起始元素的地址。
您还可以自行分配该内存块,以便为x
整数创建存储空间,例如: int *namep = malloc (x * sizeof (int));
(首选等效项为int *namep = (x * sizeof *namep);
)此处namep
指向内存块的起始地址,其大小足以容纳x
整数,就像{{1}但确实存在重要差异。
int name[x]
(例如sizeof (an array type)
返回数组中的字节数。但是sizeof (name)
(例如sizeof (a pointer)
- 或只是sizeof (namep)
)只返回 - sizeof namep
指针 - 在x86上通常为sizeof
,在x86_64上为4-bytes
。
当作为参数传递给函数时,第一级数组间接转换为指针。例如,如果您将8-bytes
作为参数传递给函数,例如功能
name
您可以将void print_array (int *array, size_t size)
{
size_t i;
for (i = 0; i < size; i++)
printf ("array[%2zu] : %d\n", i, array[i]);
}
作为name
传递给该函数。这里,数组print_array (name, x)
在传递给name[x]
时会自动转换为指向其第一个元素的指针。由于print_array
已经是指针,因此不会进行转换,因此您只需namep
即可进行转换。为什么转换很重要?
如果您按照以下方式修改了函数并将print_array (namep, x)
传递给它,会发生什么?:
name
void print_array (int *array)
{
int i,
n = (int)(sizeof array / sizeof *array);
for (i = 0; i < n; i++)
printf ("array[%2zu] : %d\n", i, array[i]);
}
实际上会保留元素的数量吗? (答案:否)。回想一下,当数组n
作为参数传递时,它被转换为指针。所以在这个不正确的版本name
只是sizeof array
而sizeof (a pointer)
只是sizeof *array
,其中x86上的结果为sizeof (int)
x86_64(1
)上的{1/1
)或2
。
现在让我们转向你的代码 - 为什么你开始使用指向char 的指针而不是2D数组令人困惑,但它没有任何问题。但是,不要让一个三星级程序员(通常不是补充),让我们利用函数返回类型来做同样的事情并避免可疑的区别。接下来我们将使用一个简单的2D数组示例。 (注意:在下面的示例中使用指针遍历输入缓冲区有意改变,并使用后面的2D数组示例中的数组索引来演示使用指针算法或数组索引是等效的)
2/1
示例输入文件
50行50个随机字符:
#include <stdio.h>
#include <stdlib.h>
#define ARSZ 50 /* don't use magic numbers in code, define constants */
char **storepuzzle (int row, int col);
int main (void) {
char **arr = NULL;
if (!(arr = storepuzzle (ARSZ, ARSZ))) { /* validate successful fill */
fprintf (stderr, "error: storepuzzle failure.\n");
return 1;
}
for (int i = 0; i < ARSZ; i++) {
for (int j = 0; j < ARSZ; j++)
printf (" %c", arr[i][j]);
putchar ('\n');
free (arr[i]); /* don't forget to free rows when done with them */
}
free (arr); /* free pointers */
return 0;
}
char **storepuzzle (int row, int col)
{
char **tmp = NULL,
buf[ARSZ + 2] = ""; /* buf to read each line into */
int ridx = 0; /* row index */
tmp = calloc (row, sizeof *tmp); /* allocate row pointers to char * */
if (!tmp) {
fprintf (stderr, "error: memory exhausted.\n");
return NULL;
}
while (fgets (buf, sizeof buf, stdin))
{
char *p = buf; /* pointer to 1st char in buf */
int cidx = 0; /* column index */
tmp[ridx] = calloc (col, sizeof *tmp[ridx]); /* allocate col chars for row */
if (!tmp[ridx]) {
fprintf (stderr, "error: memory exhausted at tmp[%d].\n", ridx);
exit (EXIT_FAILURE);
}
while (*p && *p != '\n') /* copy each char to column */
tmp[ridx][cidx++] = *p++;
if (cidx != col) /* validate col columns filled */
fprintf (stderr, "warning: insuffient input for row[%d].\n", ridx);
ridx++;
}
if (ridx != row) { /* validate row rows filled */
fprintf (stderr, "error: insufficient number of rows filled.\n");
for (int i = 0; i < ridx; i++)
free (tmp[i]);
free (tmp);
}
return tmp;
}
示例使用/输出
$ cat dat/arr50x50.txt
jjnicknlocbvgnpzfbvbbwlfvoobyjqhkehmoupvprqvwfmcga
vhwheknsldtukdykpmefhlopgkulealszzzvjennighkjfuzjr
<snip>
hfcbxnhqooijevomkwzbudzbdwtsfimnooodbnuitcryqxkauj
ugethhibrnbeahkolebfmvhvlxsnqewklavkzddjrxfjepqptr
内存使用/错误检查
在你编写的动态分配内存的任何代码中,你有2个职责关于任何分配的内存块:(1)总是保留一个指向起始地址的指针内存块,(2)当不再需要时,它可以释放。
您必须使用内存错误检查程序,以确保您不会尝试在已分配的内存块的范围之外/之外进行写入,尝试读取或基于未初始化值的条件跳转,最后,确认您释放了已分配的所有内存。
对于Linux $ ./bin/arr50x50 <dat/arr50x50.txt
j j n i c k n l o c b v g n p z f b v b b w l f v o o b y j q h k e h m o u p v p r q v w f m c g a
v h w h e k n s l d t u k d y k p m e f h l o p g k u l e a l s z z z v j e n n i g h k j f u z j r
<snip>
h f c b x n h q o o i j e v o m k w z b u d z b d w t s f i m n o o o d b n u i t c r y q x k a u j
u g e t h h i b r n b e a h k o l e b f m v h v l x s n q e w k l a v k z d d j r x f j e p q p t r
是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。
valgrind
始终确认已释放已分配的所有内存并且没有内存错误。
使用静态声明的2D数组
这里,您可以简单地返回一个整数值,其中$ valgrind ./bin/arr50x50 <dat/arr50x50.txt
==21813== Memcheck, a memory error detector
==21813== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==21813== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==21813== Command: ./bin/arr50x50
==21813==
j j n i c k n l o c b v g n p z f b v b b w l f v o o b y j q h k e h m o u p v p r q v w f m c g a
v h w h e k n s l d t u k d y k p m e f h l o p g k u l e a l s z z z v j e n n i g h k j f u z j r
<snip>
h f c b x n h q o o i j e v o m k w z b u d z b d w t s f i m n o o o d b n u i t c r y q x k a u j
u g e t h h i b r n b e a h k o l e b f m v h v l x s n q e w k l a v k z d d j r x f j e p q p t r
==21813==
==21813== HEAP SUMMARY:
==21813== in use at exit: 0 bytes in 0 blocks
==21813== total heap usage: 51 allocs, 51 frees, 2,900 bytes allocated
==21813==
==21813== All heap blocks were freed -- no leaks are possible
==21813==
==21813== For counts of detected and suppressed errors, rerun with: -v
==21813== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
表示失败,任何其他值表示成功/失败,而不是返回指示成功/失败的指针成功,例如
0
使用/输出相同 - 无需验证内存使用
除非您需要或想要动态声明指向char 的指针,否则使用2D数组可以简化操作。您可以通过将#include <stdio.h>
#include <stdlib.h>
#define ARSZ 50 /* don't use magic numbers in code, define constants */
int storepuzzle (char (*array)[ARSZ], int row, int col);
int main (void) {
char arr[ARSZ][ARSZ] = {{0}}; /* actual 2D array initialized to zeros */
if (!(storepuzzle (arr, ARSZ, ARSZ))) { /* validate successful fill */
fprintf (stderr, "error: storepuzzle failure.\n");
return 1;
}
for (int i = 0; i < ARSZ; i++) {
for (int j = 0; j < ARSZ; j++)
printf (" %c", arr[i][j]);
putchar ('\n');
}
return 0;
}
int storepuzzle (char (*array)[ARSZ], int row, int col)
{
char buf[ARSZ + 2] = ""; /* buf to read each line into */
int ridx = 0; /* row index */
while (fgets (buf, sizeof buf, stdin))
{
int cidx = 0; /* column index */
for (int i = 0; buf[i] && buf[i] != '\n'; i++, cidx++)
array[ridx][cidx] = buf[i]; /* copy each char to column */
if (cidx != col) { /* validate col columns filled */
fprintf (stderr, "warning: insuffient input for row[%d].\n", ridx);
return 0; /* return 0, indicating failure */
}
ridx++;
}
if (ridx != row) { /* validate row rows filled */
fprintf (stderr, "error: insufficient number of rows filled.\n");
return 0; /* return failure */
}
return ridx;
}
声明为指向char [50] 数组的指针来混合两者进行动态分配,但那是另一天......
仔细检查,了解这两种方法,如果您有任何问题,请告诉我。你原来的罪未能通过arr
而不是*arr[0][size] = c;
来理解C-Operator的优先级,但是因为你想避免成为 3星级程序员,所以留下来你作为一个练习。