关于C中数组的概念(基本)问题

时间:2017-12-27 04:00:12

标签: c arrays cs50

我对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如何附加到数组工作,概念上

这很有效,但我只是想知道:

  1. 当我使用unsigned char缓冲区[512]时,正如我在这种情况下所做的那样,它可以正常工作,但是当我尝试使用char缓冲区[512]时,它因分段错误而中断,所以我只是想知道什么&#39; char和unsigned char数组之间在内存方面的区别?

  2. 我在概念上有点困惑,为什么这会起作用,因为我一直认为数组有固定的大小,但在这种情况下:

    char filename[8]; sprintf(filename, "%03d.jpg", count); img = fopen(filename, "a"); count++;

  3. 我不确定发生了什么,因为我在一块内存上打开一个数组,然后......到那个数组?

    部分代码是通过视频演练提供给我们的,但是现在我已经完成了它,我对某些概念感到有点困惑 - 感谢我能提供的任何帮助得到!

2 个答案:

答案 0 :(得分:1)

  

1为什么它的&amp;缓冲区而不是缓冲区//#2如何附加到数组工作无关紧要

好的,我们走了:C中的数组在很多方面在概念上是一个相当混乱的东西。它们与原始类型(例如charint)共享一些特征,因为它们本质上是值,它们在堆栈上重新分配,并且它们在自动解除分配时会自动解除分配。它们超出范围,所以你不必担心释放它们。但是,他们与指针共享接口;使用方括号访问数组的第一个,第二个,第三个等元素的语法与访问堆中缓冲区的第一个,第二个,第三个等元素的语法相同一个指针。这本身并不一定非常混乱;如果在两种情况下该接口都有意义,那么共享类似接口的两种不同类型并不是不合理的。

然而:阵列有一小部分&#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),传递数组都会获得相同的值,这仅仅是因为数组的工作方式。您可以,也可能应该传递一个数组,就好像它是一个指针,没有符号。但是,您应该始终了解您是否正在处理指针或数组,以避免导致未定义的行为。

  

这很有效,但我只是想知道:

     
      
  1. 当我使用unsigned char缓冲区[512]时,正如我在这种情况下所做的那样,它可以工作,但是当我尝试使用char缓冲区[512]时,由于分段错误而中断,所以我只是想知道什么&#39;在内存方面char和unsigned char数组之间的区别?
  2.   

&更改为char不应导致崩溃。撞到了什么线?

  
      
  1. 我在概念上有点困惑,为什么这会起作用,因为我一直认为数组有固定的大小,但在这种情况下:

         

    char filename [8];   sprintf(filename,&#34;%03d.jpg&#34;,count);   img = fopen(filename,&#34; a&#34;);   计数++;

  2.   

这个数组确实有一个固定的大小。您的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。

我不知道“附加到数组”究竟是什么意思,但我会尝试回答: 数组是固定大小的内存块,连续存储在内存中。

  1. 如果您想在不超出其大小的情况下向其添加更多数据,则有很多方法可以做到这一点。一种是简单地保持指向数组的下一个索引的指针(从0开始),每次存储更多数据时,都会增加指针的写入量。
  2. 如果您的数据量超过了数组大小,则应使用动态分配的数组并根据需要重新分配。
  3. unsigned char buffer [512]和char buffer [512]在内存中的存储完全相同。但请注意,您使用signed char,存储溢出的值。

    char c = 0xff // Overflow
    unsigned char c = 0xff // OK
    

    但这与分段错误无关。

    您的问题的最后部分未被理解。