简体   繁体   English

动态内存和指针参数

[英]Dynamic memory and pointers arguments

I have this two functions, meant to do the same thing - read every line from a file with only integers and store them on an array: 我有这两个功能,目的是做相同的事情-仅从整数读取文件中的每一行并将它们存储在数组中:

I call them like this on the main() function: 我在main()函数上这样称呼他们:

StoreinArray1(X, size, f);

StoreinArray2(X, size, f);

The First works but the Second doesn't. 一个有效,但第二个无效。

First 第一

int StoreinArray1(int X[], int *size, char *file)
{
    int i=0;
    FILE *f;
    f = fopen(file, "r");

    X = (int*) realloc (X, *size * sizeof(int));

for (i=0;i<*size;i++)
{
    fscanf(f, "%d", &X[i]);
}

return 1;
}

Second 第二

int StoreinArray2(int X[], int *size, char *file)
{
  FILE *f;
  f = fopen(file, "r");
  if (f == NULL)
     return -1;  // failed opening
   *size = 0;
   while (!feof(f))
   {
     if (fscanf(f, "%d", &X[*size]) == 1)
       *size = *size + 1;
   }
   fclose(f);
   return 1;
}

For the First I used dynamic memory allocation and actually calculated size: 首先,我使用了动态内存分配和实际计算的大小:

 X = malloc(0); 

 while ((ch = fgetc(f)) != EOF)
{
    if (ch == '\n')
    lines++;
}

size = &lines;

For the Second I can't do the same. 对于第二,我不能做同样的事情。 Visual Studio Code crashes when I try. 我尝试时,Visual Studio代码崩溃。

So I tried to do *size = 0 and then StoreinArray2(X, size, f); 所以我尝试做*size = 0 ,然后是StoreinArray2(X, size, f); but it didn't work either. 但它也不起作用。

So my question is about the second function: 所以我的问题是关于第二个功能:

Is it calculating the size while it is scanning the file? 它正在扫描文件时计算大小吗? Supposedly it isn't necessary to use dynamic memory allocation (my teacher said). 据说没有必要使用动态内存分配(我的老师说)。

If so then how can I pass some "size" argument correctly? 如果是这样,我如何正确传递一些“大小”参数? As a pointer or just a simple integer? 作为一个指针还是一个简单的整数?

Thank you in advance! 先感谢您!


Edit: 编辑:

Here is the full First program: 这是完整的First程序:

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *f;
int *size=0, *X, lines=1;
char *file = {"file.txt"};
char ch;

X = malloc(0);

f = fopen(file, "r");

while ((ch = fgetc(f)) != EOF)
{
    if (ch == '\n')
    lines++;
}

size = &lines;

  StoreinArray(X, size, file);

}

int StoreinArray(int X[], int *size, char *file)
{
int i=0;
FILE *f;
f = fopen(file, "r");

X = (int*) realloc (X, *size * sizeof(int));

for (i=0;i<*size;i++)
{
    fscanf(f, "%d", &X[i]);
}

for (i=0;i<*size;i++)
    printf("%d\n",X[i]);

return 1;
}

And the Second: 第二:

int main()
{
    int X[100];
    int *size;
  char *file = {"file.txt"};

  *size = 0;

  StoreinArray(X, size, file);
}
int StoreinArray(int X[], int *size, char *file)
{
  FILE *f;
  f = fopen(file, "r");
  if (f == NULL)
    return -1;
  *size = 0;
  while (!feof(f))
 {
    if (fscanf(f, "%d", &X[*size]) == 1)
      *size = *size + 1;
  }
  fclose(f);
  return 1;
}

In first I had to open the file in main to count the number of lines. 首先,我必须在main中打开文件以计算行数。 I know I forgot fclose(f) and free(X) in main, but with those instructions VSC crashes. 我知道我忘记了main中的fclose(f)和free(X),但是有了这些指令,VSC崩溃了。


  int StoreinArray (int X[], int *size, char *file)
    {
      FILE *f;
      int i=0;
      f = fopen(file, "r");
       if (f == NULL)
       return -1;
     *size = 0;
      while (!feof(f))
      {
        if (fscanf(f, "%d", &X[*size]) == 1)
        {
           *size = *size + 1;
           X = (int *) realloc (X , *size * sizeof(int));
        }
      }
      fclose(f);
      return 1;
      }

       int main() 
   {
        int *X, size=0;
        char *file = {"f.txt"};
        X=malloc(0);
        StoreinArray(X, &size, file);
        free(X); 
   }

The problem with the second version of your program is the declaration of size in main. 程序第二个版本的问题是main中的size声明。 Declare it as an int, not a pointer to an int. 将其声明为int,而不是指向int的指针。 Your current program is crashing because you didn't allocate any space for size, and when StoreInArray tried to update it, you got an access violation. 当前的程序崩溃是因为您没有为大小分配任何空间,而StoreInArray尝试更新它时,您遇到了访问冲突。 So, main should look like this: 因此,main应该看起来像这样:

int main()
{
    int X[100];
    int size;
    char *file = {"file.txt"};

    size = 0;

    StoreinArray(X, &size, file);
}

OK, I'll try to be through about it and explain all the things I can find. 好的,我将尽一切努力并解释所有我能找到的东西。

First of all, we need to talk about variables, pointers and memory, because it seems that you don't have a very firm grasp of these concepts. 首先,我们需要讨论变量,指针和内存,因为您似乎对这些概念不太了解。 Once that clicks, the rest should follow easily. 一旦点击,其余的应该很容易地跟随。

First up, simple variables. 首先,简单变量。 That part is easy, I think you more or less understand that. 这部分很简单,我想您或多或少都了解这一点。

int x; // This is an integer variable. It's not initialized, so its value could be anything
int meaning = 42; // This is another integer variable. Its value will be 42.
double pi = 3.14; // A variable with digits after the decimal point
char c = 15; // Another inte... well, yes, actually, char is also an integer.
char c2 = 'a'; // Nice, this also counts. It's converted to an integer behind the scenes.

Etc. 等等。

Similarly with arrays: 与数组类似:

int arr[10]; // Array with 10 values. Uninitialized, so they contain garbage.
int arr2[3] = { 1, 2, 3 }; // Array with 3 values, all initialized
int arr3[] = {1, 2, 3, 4, 5}; // Array with 5 values.

Arrays are basically just a bunch of variables created at once. 数组基本上只是一次创建的一堆变量。 When you make an array, C needs to know the size, and the size must be a fixed number - you can't use another variable. 创建数组时,C需要知道大小,并且大小必须为固定数字-您不能使用其他变量。 There's a reason for this, but it's technical and I won't go into that. 这是有原因的,但它是技术性的,我将不赘述。

Now about memory. 现在关于内存。 Each of these variables will be stored somewhere in your computer's RAM. 这些变量中的每一个都将存储在计算机RAM中的某个位置。 The precise location is unpredictable and can vary each time you run your program. 精确的位置是无法预测的,并且每次运行程序时都可能会有所不同。

Now, RAM is like a huuuge array of bytes. 现在,RAM就像一个巨大的字节数组。 There's byte number 0 , byte number 1 , etc. An int variable takes up 4 bytes, so it could, for example, end up in bytes number 120 , 121 , 122 and 123 . 有字节号0 ,字节号1等的int变量占用4个字节,因此它可能,例如,以字节数结束120121122123

All the bytes in a single variable (or in a single array) will be next to each other in RAM. 单个变量(或单个数组)中的所有字节在RAM中将彼此相邻。 Two different variables could end up in the opposite ends of your RAM, but the bytes in each of those variables will be together. 两个不同的变量可能会出现在RAM的另一端,但是每个变量中的字节将在一起。

Now we come to the concept of a pointer. 现在我们来谈谈指针的概念。 A pointer is basically just an integer variable. 指针基本上只是一个整数变量。 It contains the RAM-number of the first byte of some other variable. 它包含其他一些变量的第一个字节的RAM号。 Let's look at an example: 让我们看一个例子:

int i = 42;
int *p = &i;

Suppose that the variable i got stored in the bytes number 200 ... 203 (that's 4 bytes). 假设变量i存储在字节数200 ... 203 (即4个字节)中。 In those bytes we have the value 42 . 在这些字节中,我们的值为42 Then let's suppose that the variable p got stored in the bytes number 300 ... 303 (that's another 4 bytes). 然后,假设变量p存储在字节数300 ... 303 (这是另外4个字节)中。 Well, these 4 bytes will contain the value 200 , because that's the first byte of the i variable. 好吧,这4个字节将包含值200 ,因为这是i变量的第一个字节。

This is also what programmers mean when they say "(memory) address of <variable> " or "a pointer to <variable> . It's the number of the first byte in RAM of <variable> . Since all bytes of the same variable stick together, then by knowing the first byte (and knowing the type of the variable), you can figure out where the rest of <variable> is in memory. 这也是什么程序员的意思,当他们说“的(内存)地址<variable> ”或”指针<variable> ,它是在RAM中的第一个字节的数量<variable> 。因为同一个变量棒的所有字节一起,然后通过了解第一个字节(并了解变量的类型),可以弄清楚<variable>其余部分在内存中的位置。

Now let's add one more line in our example: 现在,在示例中再添加一行:

*p = 5;

What the computer does in this case is it takes the address which was stored in p , goes to that location in memory, treats the following 4 bytes as an integer, and puts the value 5 there. 在这种情况下,计算机要做的是获取存储在p的地址,然后转到内存中的该位置,将随后的4个字节视为整数,然后将值5放入其中。 Since we had previously set p to "point" at the address of i , this has the same effect as simply setting the i variable itself. 由于我们先前已将p设置为“指向” i的地址,因此其效果与简单地设置i变量本身相同。

Ok, did you get all of this? 好吧,你明白了吗? This is a bit tricky and usually takes a while to wrap your head around it. 这有点棘手,通常需要一段时间才能将头缠住。 Feel free to re-read it as many times as necessary to understand it. 随意重新阅读它以理解它。 You'll need it to move on. 您将需要它继续前进。

Ready? 准备? Ok, let's talk a bit about stack and dynamic memory. 好的,让我们谈谈堆栈和动态内存。

When the program starts, the OS automatically allocates a bit of memory for it, just to make it easier for it to start. 程序启动时,操作系统会自动为其分配一些内存,以使其更易于启动。 It's like a big array of bytes, all together in memory. 就像一个大字节数组,所有字节都在内存中。 Nowadays it's usually about 1MB, but it can vary. 如今,它通常约为1MB,但可能会有所不同。 This memory is called "The Stack". 该存储器称为“堆栈”。 Why is it called that? 为什么这样称呼它? Eh, I'll explain some other time. 嗯,我再解释一遍。

Anyways, When your main() function starts, the OS goes like "Here you go my good fellow, a pointer to The Stack. It's all yours to use as you see fit! Have a nice day!" 无论如何,当您的main()函数启动时,操作系统就像“在这里,您是我的好朋友,一个指向The Stack的指针。您将视需要使用所有这些!祝您愉快!”

And your main() function then uses it to store all the variables you make in it. 然后,您的main()函数将使用它来存储您在其中所做的所有变量。 So when you go like p = &i; 所以当你像p = &i; then the address you get stored in p is somewhere within The Stack. 那么您存储在p的地址在The Stack中。

Now when main() calls another function, such as StoreinArray() , it also gives it a pointer to the stack and says "OK, here's a pointer to the stack. Careful, I've already used the first XXX bytes of it, but feel free to use the rest". 现在,当main()调用另一个函数(例如StoreinArray() ,它还会为它提供一个指向堆栈的指针,并说:“好,这是指向堆栈的指针。小心,我已经使用了它的前XXX个字节,但请随时使用其余的内容。”

And then StoreinArray() uses the stack to put its variables there. 然后, StoreinArray()使用堆栈将其变量放在此处。 And when StoreinArray() calls something else, it does the same, and on and on. StoreinArray()调用其他内容时,它会不断执行相同的操作。

Now, there are a few things to note here: 现在,这里有几件事要注意:

  • This scheme is nice, because nobody needs to "allocate" or "deallocate" any memory. 这种方案很好,因为没有人需要“分配”或“取消分配”任何内存。 It's easier, it's faster. 更简单,更快。
  • However, when a function returns, all its variables are considered to be gone and that memory is fair game to anyone else later wanting it. 但是,当一个函数返回时,它的所有变量都被认为已经消失,并且该内存对于以后想要它的其他任何人都是公平的。 So be careful about pointers pointing to it. 所以要小心指向它的指针。 They will only be valid while the function is running. 它们仅在函数运行时才有效。 When it returns - well, the pointer will still "work", but who knows when something will overwrite that memory... And if you write to that memory, who can tell what you will mess up? 当它返回时-指针仍然会“工作”,但是谁知道什么时候什么东西会覆盖该内存...而如果您写入该内存,谁能告诉您您将弄糟什么呢? Many subtle bugs have been created this way. 通过这种方式创建了许多细微的错误。
  • The stack is fairly limited and can be exhausted. 堆栈是相当有限的,可以耗尽。 When all bytes in it are used up, your program WILL crash. 当其中的所有字节用完时,您的程序将崩溃。 The typical way this happens is either when you try to create a very large array, or you go into some sort of infinite loop where the function keeps calling itself over and over again. 发生这种情况的典型方式是,当您尝试创建一个非常大的数组时,或者您进入某种无限循环时,该函数不断地反复调用自身。 Try that. 试试看 :) :)

So, for these cases you use "dynamic" memory. 因此,对于这些情况,您使用“动态”内存。 In C that mostly means malloc() . 在C语言中,这主要意味着malloc() You tell malloc() how many bytes of memory you need, and malloc() finds a large enough unclaimed space in the RAM, marks it as used, and gives you a pointer to it. 您告诉malloc()需要多少字节的内存,然后malloc()在RAM中找到足够大的未声明空间,将其标记为已使用,并为您提供一个指向它的指针。 Well, that's a simplified view of things anyway. 好吧,这还是事物的简化视图。

The same approach works when you don't know beforehand how much memory you will need. 当您事先不知道需要多少内存时,也可以使用相同的方法。

The downside is that you need to free() the memory when you're done with it, or you may run out of available memory, and then malloc() will fail. 不利的一面是,使用完内存后,您需要free()内存,否则可能会耗尽可用内存,然后malloc()将失败。 Be also aware that after you've freed the memory, all pointers to it should be considered invalid, because you're not the owner of that particular piece of memory anymore. 还应注意,释放内存后,所有指向该指针的指针都应视为无效,因为您不再是该特定内存的所有者。 Anything can happen if you keep messing with it. 如果您不停地进行任何操作,则可能会发生任何事情。

Phew, that's a lot. 哎呀,很多。 OK, I need a break. 好,我需要休息一下。 I'll come back and analyze your programs a bit later. 稍后,我将返回并分析您的程序。 But if you've understood all of this, you should now be able to spot the mistakes in your programs. 但是,如果您已经了解了所有这些内容,现在应该可以在程序中发现错误。 Try going through them, line by line, and narrating to yourself what each line does. 尝试逐行浏览它们,并向自己讲述每行的功能。

Many, many hours later: 许多小时后:

OK, so let's take a look at your programs. 好的,让我们看一下您的程序。 The first one: 第一个:

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *f;
int *size=0, *X, lines=1;
char *file = {"file.txt"};
char ch;

X = malloc(0);

f = fopen(file, "r");

while ((ch = fgetc(f)) != EOF)
{
    if (ch == '\n')
    lines++;
}

size = &lines;

  StoreinArray(X, size, file);

}

int StoreinArray(int X[], int *size, char *file)
{
int i=0;
FILE *f;
f = fopen(file, "r");

X = (int*) realloc (X, *size * sizeof(int));

for (i=0;i<*size;i++)
{
    fscanf(f, "%d", &X[i]);
}

for (i=0;i<*size;i++)
    printf("%d\n",X[i]);

return 1;
}

There are two things that can be improved here. 这里有两点可以改进。 First - the size and lines variables. 首先- sizelines变量。 There's no need for both of them. 两者都不需要。 Especially since you set size to point to lines anyway. 特别是因为您将size设置为始终指向lines Just keep lines and all will be fine. 只要保持lines ,一切都会好起来的。 When you pass it to StoreinArray() , pass it as a simple integer. 当您将其传递给StoreinArray() ,将其作为一个简单的整数传递。 There's no need for a pointer. 不需要指针。

Second, the X array. 其次, X数组。 You're doing something odd with it and it seems like you're fumbling in the dark. 您正在做一些奇怪的事情,似乎您在黑暗中摸索。 There's no need for the malloc(0) and then later realloc(X, *size*sizeof(int) . Keep it simple - first count the lines, then allocate the memory (as much as needed). Also, keep the memory allocation in the main() method and just pass the final X to the StoreinArray . This way you can avoid another subtle bug - when inside the StoreinArray() function you execute the line X = (int*) realloc (X, *size * sizeof(int)); the value of X changes only inside the StoreinArray() function. When the function returns, the variable X in the main() function will still have its old value. You probably tried to work around this with the reallocate() dance, but that's not how it works. Even worse - after the realloc() , whatever value the X used to be, isn't a valid pointer anymore, because realloc() freed that old memory! If you had later tried to do anything with the X variable in the main() function, your program would have crashed. 不需要malloc(0)然后再需要realloc(X, *size*sizeof(int) 。保持简单-首先计算行数,然后分配内存(根据需要)。在main()方法中,然后将最后的X传递给StoreinArray 。这样可以避免另一个细微的错误-在StoreinArray()函数内部时,执行X = (int*) realloc (X, *size * sizeof(int)); X的值仅在StoreinArray()函数内部更改。当函数返回时, main()函数中的变量X仍将具有其旧值。您可能试图通过reallocate()跳舞,但这不是它的工作方式,更糟糕的是-在realloc()X过去的任何值都不再是有效的指针,因为realloc()释放了旧内存!使用main()函数中的X变量执行任何操作,您的程序将崩溃。

Let's see how your program would look with the changes I proposed (plus a few more small cosmetic tweaks): 让我们看看您的程序在我提出的更改后的外观(以及一些其他的外观上的细微调整):

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *file = "file.txt";
    FILE *f = fopen(file, "r");
    int *X, lines=1;
    char ch;

    while ((ch = fgetc(f)) != EOF)
    {
        if (ch == '\n')
            lines++;
    }
    fclose(f);

    X = (int *)malloc(lines * sizeof(int));

    StoreinArray(X, lines, file);
}

void StoreinArray(int X[], int lines, char *file)
{
    int i=0;
    FILE *f = fopen(file, "r");

    for (i=0;i<lines;i++)
    {
        fscanf(f, "%d", &X[i]);
    }
    fclose(f);

    for (i=0;i<lines;i++)
        printf("%d\n",X[i]);
}

OK, now the second program. 好,现在是第二个程序。

int main()
{
    int X[100];
    int *size;
  char *file = {"file.txt"};

  *size = 0;

  StoreinArray(X, size, file);
}
int StoreinArray(int X[], int *size, char *file)
{
  FILE *f;
  f = fopen(file, "r");
  if (f == NULL)
    return -1;
  *size = 0;
  while (!feof(f))
 {
    if (fscanf(f, "%d", &X[*size]) == 1)
      *size++;
  }
  fclose(f);
  return 1;
}

Right off the bat, the size variable will crash your program. 马上, size变量将使程序崩溃。 It's a pointer that isn't initialized so it points to some random place in memory. 这是一个未初始化的指针,因此它指向内存中的某个随机位置。 When a little lower you try to write to the memory it points to ( *size = 0 ), that will crash, because most likely you won't own that memory. 当您尝试将其写入的内存略低时,它指向( *size = 0 ),这将崩溃,因为您很可能不会拥有该内存。 Again, you really don't need a pointer here. 同样,您实际上不需要在这里指向指针。 In fact, you don't need the variable at all. 实际上,您根本不需要该变量。 If you need to know in the main program how many integers the StoreinArray() read, you can simply have it return it. 如果您需要在主程序中知道StoreinArray()读取了多少个整数,可以简单地让它返回它。

There's another subtle problem - since the size of X array is fixed, you cannot afford to read more than 100 integers from the file. 还有一个细微的问题-由于X数组的大小是固定的,因此您不能负担得起从文件中读取100个以上的整数。 If you do, you will go outside the array and your program will crash. 如果这样做,您将走出阵列,程序将崩溃。 Or worse - it won't crash, but you'll be overwriting the memory that belongs to some other variable. 或更糟糕的是-它不会崩溃,但是您将覆盖属于其他变量的内存。 Weird things will happen. 奇怪的事情将会发生。 C is lenient, it doesn't check if you're going outside the allowed bounds - but if you do, all bets are off. C是宽大的,它不会检查您是否超出允许的范围-但是,如果您这样做,则所有赌注都无效。 I've spent many hours trying to find the cause of a program behaving weirdly, only to find out that some other code in some completely unrelated place had gone outside its array and wreaked havoc upon my variablers. 我花了很多时间试图找出导致程序异常的原因,却发现某个完全不相关的地方的其他一些代码超出了它的范围,并给我的变量带来了严重破坏。 This is VERY difficult to debug. 这很难调试。 Be VERY, VERY careful with loops and arrays in C. 请非常小心C中的循环和数组。

In fact, this kind of bug - going outside an array - has it's own name: a "Buffer Overrun". 实际上,这种错误-超出数组范围-拥有自己的名称:“缓冲区溢出”。 It's a very common security exploit too. 这也是一个非常常见的安全漏洞。 Many security vulnerabilities in large, popular programs are exactly this problem. 大型流行程序中的许多安全漏洞正是这个问题。

So, the best practice would be to tell StoreinArray() that it can store at most 100 integers in the X array. 因此,最佳实践是告诉StoreinArray()它最多可以在X数组中存储100个整数。 Let's do that: 让我们这样做:

#include <stdio.h>
#include <stdlib.h>

#define MAX_X 100

int main()
{
    int X[MAX_X];
    char *file = "file.txt";
    int lines;

    lines = StoreinArray(X, MAX_X, file);
}
int StoreinArray(int X[], int maxLines, char *file)
{
    FILE *f;
    int lines;

    f = fopen(file, "r");
    if (f == NULL)
        return -1;

    while (!feof(f))
    {
        if (fscanf(f, "%d", &X[lines]) == 1)
            lines++;
        if (lines == maxLines)
            break;
    }
    fclose(f);
    return lines;
}

So, there you are. 所以,你在那里。 This should work. 这应该工作。 Any more questions? 还有其他问题吗? :) :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM