OSTEP-第十四章

14.1

首先,编写一个名为null.c的简单程序,它创建一个指向整数的指针,将其设置为NULL,然后尝试对其进行释放内存操作。把它编译成一个名为null的可执行文件。当你运行这个程序时会发生什么?

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

int main() {
  int *null = NULL;
  free(null);
}
gcc null.c -o null
./null

什么也没发生

如果是用clion自带的构建输出,倒是会提示:cannot open output file null.exe: Invalid argument

14.2

接下来,编译该程序,其中包含符号信息(使用-g标志)。这样做可以将更多信息放入可执行文件中,使调试器可以访问有关变量名称等的更多有用信息。通过输入 gdb null,在调试器下运行该程序,然后,一旦 gdb运行,输入run。gdb显示什么信息?

gcc null.c -o null -g
gdb null

结果是:

(gdb) run
Starting program: /home/auceptin_fang/OSTEP/chapter14/null
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[Inferior 1 (process 100232) exited normally]

这对吗

14.3

最后,对这个程序使用 valgrind 工具。我们将使用属于 valgrind 的 memcheck 工具来分析发生的情况。输入以下命令来运行程序:valgrind --leak-check=yes null。当你运行它时会发生什么?你能解释工具的输出吗?

valgrind我已经安装好了

valgrind --leak-check=yes ./null

得到输出:

==103761== Memcheck, a memory error detector
==103761== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==103761== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==103761== Command: ./null
==103761==
==103761== 
==103761== HEAP SUMMARY:
==103761==     in use at exit: 0 bytes in 0 blocks
==103761==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==103761==
==103761== All heap blocks were freed -- no leaks are possible
==103761==
==103761== For lists of detected and suppressed errors, rerun with: -s
==103761== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

啊这,没有问题。在跟claude4交流之后我确认,free(null) = free (NULL),而这个操作在c里面是安全的,所以以上实验结果没有问题

14.4

4.编写一个使用 malloc()来分配内存的简单程序,但在退出之前忘记释放它。这个程序运行时会发生什么?你可以用gdb来查找它的任何问题吗?用vagrind 呢(再次使用
–leak-check=yes标志)?

会发生什么吗?操作系统在进程退出后帮我释放掉

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

int main(){
  int *p = (int*)malloc(sizeof(int));
  return 0;
}

运行一下,没有任何输出,gdb下运行,和刚才基本一致

valgrind --leak-check=yes ./14.4
==110749== HEAP SUMMARY:
==110749==     in use at exit: 4 bytes in 1 blocks
==110749==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==110749==
==110749== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==110749==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==110749==    by 0x10915E: main (in /home/auceptin_fang/OSTEP/chapter14/14.4)
==110749==
==110749== LEAK SUMMARY:
==110749==    definitely lost: 4 bytes in 1 blocks
==110749==    indirectly lost: 0 bytes in 0 blocks
==110749==      possibly lost: 0 bytes in 0 blocks
==110749==    still reachable: 0 bytes in 0 blocks
==110749==         suppressed: 0 bytes in 0 blocks
==110749==
==110749== For lists of detected and suppressed errors, rerun with: -s
==110749== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

看到它检测到堆上allocs了一次,4字节,free了0次,检测到了一个错误

14.5

编写一个程序,使用 malloc 创建一个名为 data、大小为100的整数数组。然后,将data[100]设置为0。当你运行这个程序时会发生什么?当你使用 valgrind 运行这个程序时会发生什么?程序是否正确?

我们lua程序员就没有这个事

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

int main(){
  int *data = (int*)malloc(100 * sizeof(int));
  100[data] = 0;
  return 0;
}

不要在意其中某个好玩的细节

直接运行,没有输出没有报错。用valgrind:

==117106== Invalid write of size 4
==117106==    at 0x10916D: main (in /home/auceptin_fang/OSTEP/chapter14/14.5)
==117106==  Address 0x4a771d0 is 0 bytes after a block of size 400 alloc'd
==117106==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==117106==    by 0x10915E: main (in /home/auceptin_fang/OSTEP/chapter14/14.5)
==117106==
==117106==
==117106== HEAP SUMMARY:
==117106==     in use at exit: 400 bytes in 1 blocks
==117106==   total heap usage: 1 allocs, 0 frees, 400 bytes allocated
==117106==
==117106== LEAK SUMMARY:
==117106==    definitely lost: 400 bytes in 1 blocks
==117106==    indirectly lost: 0 bytes in 0 blocks
==117106==      possibly lost: 0 bytes in 0 blocks
==117106==    still reachable: 0 bytes in 0 blocks
==117106==         suppressed: 0 bytes in 0 blocks
==117106== Rerun with --leak-check=full to see details of leaked memory
==117106==
==117106== For lists of detected and suppressed errors, rerun with: -s
==117106== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

有报错了,Invalid write of size 4,也就是说有四字节的写入是Invalid的

14.6

创建一个分配整数数组的程序(如上所述),释放它们,然后尝试打印数组中某个元素的值。程序会运行吗?当你使用 valgrind 时会发生什么?

这个CS:APP里提过,我以前也试过。释放操作是lazy的,也就是简单地标记对应地址可用,而不着急删除里面的数据直到再次被利用。所以尝试打印是会有结果的,而且很有可能是正确的

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

int main(){
    int *data = (int*)malloc(100 * sizeof(int));
    for(int i = 0; i < 100; i++){
      data[i] = i;
    }
    free(data);
    printf("%d\n", data[35]);
    return 0;
}

运行一下:

auceptin_fang@Auceptin:~/OSTEP/chapter14$ ./14.6
35

没错,是正确的。如果是上课,老师多半要补充一句不建议,但是这么好玩的东西,生产环境不敢用,平时练习不得多用用

看看valgrind怎么说:

==121990== Invalid read of size 4
==121990==    at 0x1091E5: main (in /home/auceptin_fang/OSTEP/chapter14/14.6)
==121990==  Address 0x4a770cc is 140 bytes inside a block of size 400 free'd
==121990==    at 0x484988F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==121990==    by 0x1091DA: main (in /home/auceptin_fang/OSTEP/chapter14/14.6)
==121990==  Block was alloc'd at
==121990==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==121990==    by 0x10919E: main (in /home/auceptin_fang/OSTEP/chapter14/14.6)
==121990==
35
==121990== 
==121990== HEAP SUMMARY:
==121990==     in use at exit: 0 bytes in 0 blocks
==121990==   total heap usage: 2 allocs, 2 frees, 1,424 bytes allocated
==121990==
==121990== All heap blocks were freed -- no leaks are possible
==121990==
==121990== For lists of detected and suppressed errors, rerun with: -s
==121990== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

还是非法访问呗

14.7

现在传递一个有趣的值来释放(例如,在上面分配的数组中间的一个指针)。会发生什么?你是否需要工具来找到这种类型的问题?

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

int main(){
    int *data = (int*)malloc(100 * sizeof(int));
    for(int i = 0; i < 100; i++){
        data[i] = i;
    }
    free(data + 35);
    return 0;
}

虽然free预期一个指针,但正如书中提到,要求是malloc返回的指针,如果不是的话——

编译时有了warning

14.7.cpp: In function ‘int main()’:
14.7.cpp:9:9: warning: ‘void free(void*)’ called on pointer ‘data’ with nonzero offset 140 [-Wfree-nonheap-object]
    9 |     free(data + 35);
      |     ~~~~^~~~~~~~~~~
14.7.cpp:5:29: note: returned from ‘void* malloc(size_t)’
    5 |     int *data = (int*)malloc(100 * sizeof(int));
      |                       ~~~~~~^~~~~~~~~~~~~~~~~~~

运行有了报错

free(): invalid pointer
Aborted (core dumped)

试一下valgrind:

==131046== Invalid free() / delete / delete[] / realloc()
==131046==    at 0x484988F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==131046==    by 0x1091C0: main (in /home/auceptin_fang/OSTEP/chapter14/14.7)
==131046==  Address 0x4a770cc is 140 bytes inside a block of size 400 alloc'd
==131046==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==131046==    by 0x10917E: main (in /home/auceptin_fang/OSTEP/chapter14/14.7)
......
==131046== For lists of detected and suppressed errors, rerun with: -s
==131046== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

14.8

尝试一些其他接口来分配内存。例如,创建一个简单的向量似的数据结构,以及使用realloc()来管理向量的相关函数。使用数组来存储向量元素。当用户在向量中添加条目时,请使用realloc()为其分配更多空间。这样的向量表现如何?它与链表相比如何?使用valgrind来帮助你发现错误

这里的向量是在说vector吗,那还不如不翻译呢。提前到来的数据结构实践,参考去年学链表的经验了。

#include<stdio.h>
#include<stdlib.h>
typedef struct{
  size_t size;
  size_t used_size;
  int* data;
}vector;

vector* vector_init(size_t size){    //只支持输入大小初始化,且初始化为0
  vector* v = (vector*)malloc(sizeof(vector));
  v->data = (int*)calloc(size, sizeof(int));
  v->size = size;
  v->used_size = 0;
  return v;
}

vector* vector_resize(vector* v, size_t new_size){
  v->data = (int*)realloc(v->data, sizeof(int)*new_size);
  v->size = new_size;
  return v;
}

void vector_push_back(vector* v, int value){
  if(v->used_size == v->size){
    v = vector_resize(v , v->size * 2);
  }
  v->data[v->used_size] = value;
  v->used_size += 1;
}

int vector_pop_back(vector* v){
  if(v->used_size == 0){
    return 0;
  }
  v->used_size -= 1;
  return v->data[v->used_size];
}

int main(){
  vector* v = vector_init(5);

  // 向 vector 中添加一些元素
  vector_push_back(v, 10);
  vector_push_back(v, 20);
  vector_push_back(v, 30);
  vector_push_back(v, 40);
  vector_push_back(v, 50);
  vector_push_back(v, 60);  // 由于容量为 5,推入第 6 个时会自动扩容

  // 打印 vector 中的元素
  printf("Vector contents after push_back operations:\n");
  for (size_t i = 0; i < v->used_size; i++) {
    printf("%d ", v->data[i]);
  }
  printf("\n");

  // 弹出最后一个元素
  int popped_value = vector_pop_back(v);
  printf("Popped value: %d\n", popped_value);

  // 打印 vector 中的元素
  printf("Vector contents after pop_back:\n");
  for (size_t i = 0; i < v->used_size; i++) {
    printf("%d ", v->data[i]);
  }
  printf("\n");

  // 释放内存
  free(v->data);
  free(v);

  return 0;
}

实现完成,接下来是和链表对比。

在末尾增删元素:都是O(1),其余的……没做