释放在C中具有未关闭文件的结构。

huangapple go评论74阅读模式
英文:

Freeing struct with unclosed file in C

问题

typedef struct {
  FILE* file;
  ...
  ...
} MyStruct;

void free_mystruct(MyStruct* s) {
  if (!s) {
    return;
  }

  if (!s->file) {
    free(s);
    s = NULL;
    return;
  }

  if (fclose(s->file) != 0) {
    // it failed to close the file...
    // how to handle the free?
    // 如果关闭文件失败,如何处理释放内存?
  }

  free(s);
  s = NULL;
}

MyStruct* mystruct_new(const char* filename) {
  MyStruct* ms = malloc(sizeof(MyStruct));
  if (!ms) {
    return NULL;
  }

  FILE* file = fopen(filename, "w+");
  if (!file) {
    free(ms);
    ms = NULL;
    return NULL;
  }

  ms->file = file;
  return ms;
}

int main(void) {
  MyStruct* ms = mystruct_new("file.txt");
  if (!ms) {
    exit(EXIT_FAILURE);
  }

  // do something with `ms`...
  // 使用`ms`进行一些操作...

  free_mystruct(ms);

  return EXIT_SUCCESS;
}
英文:

I don't have much experience in C, my question is as follows:

if I have a struct like this:

typedef struct {
  FILE* file;
  ...
  ...
} MyStruct;

and a function to free this struct:

void free_mystruct(MyStruct* s) {
  if (!s) {
    return;
  }

  if (!s->file) {
    free(s);
    s = NULL;
    return;
  }

  if (fclose(s->file) != 0) {
    // it failed to close the file...
    // how to handle the free?
  }

  free(s);
  s = NULL;
}

does freeing the struct anyways inside the if block where it fails to close the file leak memory or system resources? if it does how to handle the free in a situation like this should I report that it failed and don't perform the free() call and return?

I think this question comes down to the situations where fclose() fails, probably. I don't know these.

EDIT:
this is how MyStruct is initialized:

MyStruct* mystruct_new(const char* filename) {
  MyStruct* ms = malloc(sizeof(MyStruct));
  if (!ms) {
    return NULL;
  }

  FILE* file = fopen(filename, "w+");
  if (!file) {
    free(ms);
    ms = NULL;
    return NULL;
  }

  ms->file = file;
  return ms;
}

main can look like this:

int main(void) {
  MyStruct* ms = mystruct_new("file.txt");
  if (!ms) {
    exit(EXIT_FAILURE);
  }

  // do something with `ms`...

  free_mystruct(ms);

  return EXIT_SUCCESS;
}

答案1

得分: 1

在这个示例中,您正在使用malloc(sizeof(MyStruct))初始化一个MyStruct变量。因此,free_mystruct代码看起来是正确的。它应该安全地释放由mystruct_new创建的结构。

它首先会检查参数MyStruct* s是否为null,如果不是,然后会尝试使用fclose关闭FILE*变量(如果存在),最后会使用free(s)来释放使用malloc(sizeof(MyStruct))创建的内存,这是适当的方法。它还将参数MyStruct* s设置为null这有助于避免在代码的其他部分可能出现的危险的使用后释放错误

关于fclose失败

在某些情况下,您可能不需要担心fclose失败,因为您已经处理完文件了。fclose可能因各种原因而失败,但如果fclose失败,您的代码不会有内存泄漏。这个问题是关于创建和销毁MyStruct实例的,您提供的方法是_相对安全_的(使用这个结构的代码可能存在错误,但在您提供的代码中没有错误)。


更新:关于's = NULL'

在评论中已经讨论了s = NULL的实用性以及可能存在的“使用后释放”等错误。

在深入示例之前,应该注意,在free_mystruct中,我们通过值传递了一个MyStruct指针。这意味着在函数内部创建了一个新的变量,用于存储传入的指针的值。更改本地指针的值不会更改外部指针的值 - 但更改所指向的地址会(这就是free(s)起作用的原因)。

这里是一个简单的例子:

int main(void) {
  MyStruct* ms = mystruct_new("file.txt");
  if (!ms) {
    exit(1);
  }

  printf("The variable `ms` points to: %p\n", ms);
  printf("A few bytes at `ms`: %x\n", *ms);

  free_mystruct(ms);

  printf("The variable `ms` points to: %p\n", ms);
  printf("A few bytes at `ms`: %x\n", *ms);

  return 0;
}

当我运行以上代码时,我会收到类似以下的输出:

The variable `ms` points to: 0x1516069f0
A few bytes at `ms`:         fca465a0
The variable `ms` points to: 0x1516069f0
A few bytes at `ms`:                0

这是我们应该期望的。指针变量的地址不会改变,因此s = NULL不会影响该变量(在free_mystruct之外)。但是,在ms处的数据似乎已经被释放。

如果您一直跟到这里,您应该看到“使用后释放”错误仍然存在的可能性。仅仅将通过值传递的指针置为null还不足够。如果代码像下面这样做会更安全:

free_mystruct(ms);
ms = 0;

请注意,上述代码将指针设置为0,但现在由调用者来执行这个操作(或者确保已释放的MyStruct在代码的其他地方不被引用)。

这些细微差别在C语言中很常见,因为内存管理最终由开发人员负责。OP提供的代码本身没有错误,但使用它的开发人员应该注意可能导致安全漏洞的潜在误用。


更深入的探讨

如果我们再深入一层,我们可以看到free只会将动态分配地址范围的第一个字节设置为null。这就是之前输出暗示ms处的数据在释放后仍然保留在内存中的原因。在下面的示例中,我们可以看到实际情况并非如此。

int main(void) {
  // 第1步:在malloc之前的MyStruct内存
  puts("第1步");
  MyStruct* ms;
  for (int i = 0; i < sizeof(MyStruct); i++)
    printf(" %02hhx", ms[i]);
  puts("\n");

  // 第2步:在malloc之后的MyStruct内存
  puts("第2步");
  ms = mystruct_new("file.txt");
  if (!ms) {
    exit(1);
  }
  printf("The variable `ms` points to: %p\n", ms);
  printf("A few bytes at `ms`");
  for (int i = 0; i < sizeof(MyStruct); i++)
    printf(" %02hhx", ms[i]);
  puts("\n");

  // 第3步:在释放之后的MyStruct内存
  puts("第3步");
  free_mystruct(ms);
  printf("The variable `ms` points to: %p\n", ms);
  printf("A few bytes at `ms`");
  for (int i = 0; i < sizeof(MyStruct); i++)
    printf(" %02hhx", ms[i]);
  puts("\n");

  return 0;
}

示例输出:

1
 88 00 80 00 01 01 00 80

2
The variable `ms` points to: 0x14ce069f0
A few bytes at `ms` a0 00 00 00 03 00 00 00

3
The variable `ms` points to: 0x14ce069f0
A few bytes at `ms` 00 00 60 00 03 00 00 00

这里的主要要点是free(...)不会将所有在释放后的内存中的字节都设置为null。这意味着在释放结构后,动态分配位置中存储的数据可能仍然存在于内存中。

这也是为什么在释放

英文:

In this example, you are initializing a MyStruct variable with malloc(sizeof(MyStruct)). Thus, the free_mystruct code looks good as it is. It should safely free the structure created by mystruct_new.

It will first check to see if the argument MyStruct* s is null, if not then it will attempt to close the FILE* variable with fclose (if it exists), and finally it will free(s) which is the appropriate way to deallocate the memory created with malloc(sizeof(MyStruct)). It also sets the argument MyStruct* s to null, <s>which helps avoid dangerous use-after-free bugs that could appear in other parts of your code</s>.

Regarding fclose failing

In some cases you won't need to worry about fclose failing because you are done with the file. fclose can fail for a variety of reasons, but your code as-is would not have memory leaks if fclose failed. This question is about creating and destroying instances of MyStruct, and the methods you've provided for doing so are reasonably safe (bugs are possible in code that uses this structure, but are not in the code you've provided).


UPDATE: Regarding 's = NULL'

In the comments there has been discussion regarding the utility of s = NULL as well as the potential of bugs such as 'use-after-free'.

Before getting into examples, it should be noted that in free_mystruct we are passing a MyStruct pointer by value. That means a new variable is created local to the function which stores the value of the pointer that was passed in. Changing the value of the local pointer does not change the value of the external one - however, changes to the address being pointed to will (which is why free(s) works).

Here is a simple example:

int main(void) {
  MyStruct* ms = mystruct_new(&quot;file.txt&quot;);
  if (!ms) {
    exit(1);
  }

  printf(&quot;The variable `ms` points to: %p\n&quot;, ms);
  printf(&quot;A few bytes at `ms`: %x\n&quot;, *ms);

  free_mystruct(ms);

  printf(&quot;The variable `ms` points to: %p\n&quot;, ms);
  printf(&quot;A few bytes at `ms`: %x\n&quot;, *ms);

  return 0;
}

When I run the above, I receive output such as the following:

The variable `ms` points to: 0x1516069f0
A few bytes at `ms`:         fca465a0
The variable `ms` points to: 0x1516069f0
A few bytes at `ms`:                0

This is what we should expect. The address of the pointer variable doesn't change, and therefore s = NULL does not take affect on that variable (outside of the free_mystruct) function. However, the data at ms appears to have been freed.

If you're following along this far, you should see that potential for a 'use-after-free' bug does still exist. It wasn't enough to nullify the pointer that was passed by value. It would be safer if the code did something like this:

free_mystruct(ms);
ms = 0;

Notice how the above sets the pointer to 0, but it is now the burden of the caller to do this (or ensure that the free'd MyStruct isn't referenced elsewhere in the code).

These types of nuances are common in C, where memory management is ultimately up to the developer. The code that the OP provided isn't buggy itself, but the developer using it should be aware of potential misuses that could result in security bugs.


DIGGING EVEN DEEPER

If we look another layer deeper we can see that free only nullifies the first byte of a dynamically allocated address range. That is why the output from earlier implied that the data at ms was null bytes after the free. In the following example, we can see that is not actually the case.

int main(void) {
  // STEP 1: Memory at MyStruct before malloc
  puts(&quot;STEP 1&quot;);
  MyStruct* ms;
  for (int i = 0; i &lt; sizeof(MyStruct); i++)
    printf(&quot; %02hhx&quot;, ms[i]);
  puts(&quot;\n&quot;);

  // STEP 2: Memory at MyStruct after malloc
  puts(&quot;STEP 2&quot;);
  ms = mystruct_new(&quot;file.txt&quot;);
  if (!ms) {
    exit(1);
  }
  printf(&quot;The variable `ms` points to: %p\n&quot;, ms);
  printf(&quot;A few bytes at `ms`&quot;);
  for (int i = 0; i &lt; sizeof(MyStruct); i++)
    printf(&quot; %02hhx&quot;, ms[i]);
  puts(&quot;\n&quot;);

  // STEP 3: Memory at MyStruct after free
  puts(&quot;STEP 3&quot;);
  free_mystruct(ms);
  printf(&quot;The variable `ms` points to: %p\n&quot;, ms);
  printf(&quot;A few bytes at `ms`&quot;);
  for (int i = 0; i &lt; sizeof(MyStruct); i++)
    printf(&quot; %02hhx&quot;, ms[i]);
  puts(&quot;\n&quot;);

  return 0;
}

Example output:

STEP 1
 88 00 80 00 01 01 00 80

STEP 2
The variable `ms` points to: 0x14ce069f0
A few bytes at `ms` a0 00 00 00 03 00 00 00

STEP 3
The variable `ms` points to: 0x14ce069f0
A few bytes at `ms` 00 00 60 00 03 00 00 00

The main takeaway here is that free(...) does not nullify all of the bytes in the memory that was de-allocated. That means that the data stored at a dynamically allocated location may remain in memory after free(...) is called.

This is yet another reason why not setting ms = NULL after deallocating the structure could be dangerous.

huangapple
  • 本文由 发表于 2023年6月26日 23:38:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76558202.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定