我对C编程真的很陌生,而且对于我们的一项作业,我们基本上必须从存储卡中恢复jpg图像。我的代码是这样的:
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h>
// typedef uint8_t BYTE;
// #define BUFFER_SIZE 512
int main(int argc, char *argv[])
{
// ensure proper usage
if (argc != 2)
{
fprintf(stderr, "Usage: ./recover image\n");
return 1;
}
//opens the memory card for reading
FILE* mem = fopen(argv[1], "r");
if (mem == NULL)
{
fprintf(stderr, "Could not open %s.\n", argv[1]);
return 1;
}
unsigned char buffer[512];
FILE* img = NULL;
int found = 0;
int count = 0;
while (fread(&buffer, 512,1,mem) == 1)
{
if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xe0) == 0xe0)
{
if (found == 1)
fclose(img);
else
found = 1;
char filename[8];
sprintf(filename, "%03d.jpg", count);
img = fopen(filename, "a");
count++;
}
if (found ==1)
{
fwrite(&buffer,512,1,img);
}
}
fclose(mem);
fclose(img);
return 0;
}
//#1为什么它的&amp;缓冲区而不是缓冲区无关紧要 //#2如何附加到数组工作,概念上
这很有效,但我只是想知道:
当我使用unsigned char缓冲区[512]时,正如我在这种情况下所做的那样,它可以正常工作,但是当我尝试使用char缓冲区[512]时,它因分段错误而中断,所以我只是想知道什么&#39; char和unsigned char数组之间在内存方面的区别?
我在概念上有点困惑,为什么这会起作用,因为我一直认为数组有固定的大小,但在这种情况下:
char filename[8];
sprintf(filename, "%03d.jpg", count);
img = fopen(filename, "a");
count++;
我不确定发生了什么,因为我在一块内存上打开一个数组,然后......到那个数组?
部分代码是通过视频演练提供给我们的,但是现在我已经完成了它,我对某些概念感到有点困惑 - 感谢我能提供的任何帮助得到!
答案 0 :(得分:1)
1为什么它的&amp;缓冲区而不是缓冲区//#2如何附加到数组工作无关紧要
好的,我们走了:C中的数组在很多方面在概念上是一个相当混乱的东西。它们与原始类型(例如char
和int
)共享一些特征,因为它们本质上是值,它们在堆栈上重新分配,并且它们在自动解除分配时会自动解除分配。它们超出范围,所以你不必担心释放它们。但是,他们与指针共享接口;使用方括号访问数组的第一个,第二个,第三个等元素的语法与访问堆中缓冲区的第一个,第二个,第三个等元素的语法相同一个指针。这本身并不一定非常混乱;如果在两种情况下该接口都有意义,那么共享类似接口的两种不同类型并不是不合理的。
然而:阵列有一小部分&#34;魔法&#34;给他们;如果你为一个数组分配一个数组 - 无论它是变量,还是你传递给函数的参数 - 它将自动转换为指向数组第一个元素的指针。
char foo[512]; // an array of size 512
char *bar = foo; // a *pointer* to the first element in the array
这种自动转换在接近金属的情况下有点令人惊讶。像C这样的语言通常会让你拼出你将要做的事情;此外,指针和数组是如此可互换的事实使得很容易假设数组实际是指针,而实际的指针是数组。但是,它们并不相同,一个明显不同的是您在此处提出的问题的答案:无论您通过fread
还是{{1},为什么您对buffer
的调用都有效? }}?好吧,假设您有以下变量:
&buffer
假设一台int foo;
char bar[8];
int baz;
大小为4的机器,您可以想象这些机器在内存中的布局是这样的:
int
看着这种视觉抽象,你可以看到一些东西; -------------------------------------
||f¦o¦o¦ ||b¦a¦r¦ ¦ ¦ ¦ ¦ ||b¦a¦z¦ ||
||1¦2¦3¦4||1¦2¦3¦4¦5¦6¦7¦8||1¦2¦3¦4||
-------------------------------------
所在的地址(显然)与其第一个元素bar
所在的地址相同,因此,当您将b
传递给采用bar
的内容时#39; s转换为指向其第一个元素的指针,与数组本身的地址相同的地址。这就是为什么,如果你同时记录一个数组和地址在数组中,两次都得到相同的值:
char *
相比之下,如果char foo[512];
printf("%p %p\n", (void *)foo, (void *)&foo); // these will both log the same address
是指针而不是数组(也就是说,它被键入为foo
而不是char *
}),您实际上会为char []
和foo
获取不同的值,并且将&foo
传递给类似&foo
的函数将无法正常工作。这是因为与数组不同,指针不代表数据本身,而是可以被认为是指向存储在某个其他位置的数据的路标,因此,它的地址 not 数据的地址。
这种魔法存在的原因基本上是为了方便,因此您可以像使用指针一样使用数组。然而,这会给你带来新的陷阱,你必须要小心。例如,您无法从函数返回数组:
fread
你在这看到问题吗?基本上,只要我们尝试返回char *foo() {
char bar[4] = "Bar";
return bar; // This won't work. Don't do this!
}
,它就会变成指向数组中第一个元素的指针。但是,只要bar
返回,foo()
数组就会超出范围并被取消分配。调用者现在有一个指向垃圾记忆的指针。这是特别阴险的,因为似乎仍然可以工作;以前由数组占用的内存将继续包含数组所具有的任何值,直到其他东西决定覆盖该内存,并且不能保证这是否会迟早发生。这种不确定性会导致未定义的行为,这可能是非常微妙且难以追踪的错误的来源。
因此,总结一下:无论是否包含&符号(bar
),传递数组都会获得相同的值,这仅仅是因为数组的工作方式。您可以,也可能应该传递一个数组,就好像它是一个指针,没有符号。但是,您应该始终了解您是否正在处理指针或数组,以避免导致未定义的行为。
这很有效,但我只是想知道:
- 当我使用unsigned char缓冲区[512]时,正如我在这种情况下所做的那样,它可以工作,但是当我尝试使用char缓冲区[512]时,由于分段错误而中断,所以我只是想知道什么&#39;在内存方面char和unsigned char数组之间的区别?
醇>
从&
更改为char
不应导致崩溃。撞到了什么线?
- 醇>
我在概念上有点困惑,为什么这会起作用,因为我一直认为数组有固定的大小,但在这种情况下:
char filename [8]; sprintf(filename,&#34;%03d.jpg&#34;,count); img = fopen(filename,&#34; a&#34;); 计数++;
这个数组确实有一个固定的大小。您的sprintf语句生成一个字符串,其中包含以下内容:三位数字,一个句点,然后是字符&#34; jpg&#34;。这七个字符,并添加C字符串所需的空终止符会产生八个字符。
在做这样的事情时要非常小心。如果你不小心尝试写一个大于数组大小的字符串,C就不会阻止你,然后你将覆盖内存中数组之后发生的任何事情。这会导致未定义的行为,这意味着无法保证会发生什么。您的程序可能会崩溃。您可能会在程序中的其他位置静默地损坏某些数据。虫洞可能向第五维开放,导致地球被邪恶的食人空间三叶虫侵入。几乎任何事情都有,所以在使用缓冲区时要小心。
实际上,在这个程序中有一种方法可以实现;如果unsigned char
变为count
或更大,1000
会给它超过三位数,即使您只要求三位数。这将导致缓冲区溢出。在生产应用中,您要1)添加支票以确保sprintf
永远不会超过count
,2)检查999
的值以确定适当的字符串大小,而不是硬编码为8,或3)硬编码字符串的大小,以便能够保存count
可以存储的最大值的位数(开英特尔x86,即int
,十位数字,因此为点添加一个,为扩展添加三个,为终结符添加一个,您将字符串设置为15个字节长)
我原来的,对这个问题的匆匆书面答案被一些人误解了,所以现在我有更多的时间,我将其重写为尽可能清晰和详细。如果您仍然不相信,请阅读section 6 of the comp.lang.c FAQ。
答案 1 :(得分:0)
使用&amp;缓冲区和缓冲区是两回事。 &amp; buffer 为您提供缓冲区变量的内存地址,而使用缓冲区为您提供缓冲区指向的值(其值)。 对于fread,你应该只使用缓冲区(或&amp; buffer [0])而不是&amp; buffer。
我不知道“附加到数组”究竟是什么意思,但我会尝试回答: 数组是固定大小的内存块,连续存储在内存中。
unsigned char buffer [512]和char buffer [512]在内存中的存储完全相同。但请注意,您使用signed char,存储溢出的值。
char c = 0xff // Overflow
unsigned char c = 0xff // OK
但这与分段错误无关。
您的问题的最后部分未被理解。