提交 439dcc74 编写于 作者: W wizardforcel

2020-05-09 22:15:43

上级 bc272361
......@@ -25,7 +25,7 @@ char * str2 = "Brandon Chong is the best TA";
`str1``str2`指向的字符串实际上可能位于内存中的相同位置。
但是,字符数组包含已从代码段复制到栈或静态内存中的文字值。以下 char 数组不驻留在内存中的相同位置。
但是,字符数组包含已从代码段复制到栈或静态内存中的文字值。以下 char 数组不驻留在内存中的相同位置。
```c
char arr1[] = "Brandon Chong didn't write this";
......@@ -57,7 +57,7 @@ int *f() {
}
```
自动变量仅在函数的生命周期内绑定到栈内存。函数返回后,继续使用内存是错误的。
自动变量仅在函数的生命周期内绑定到栈内存。函数返回后,继续使用内存是错误的。
## 内存分配不足
......
......@@ -2,19 +2,19 @@
> 原文:<https://github.com/angrave/SystemProgramming/wiki/Processes%2C-Part-1%3A-Introduction>
## 概
## 概
进程是正在运行的程序(有点)。进程也只是该计算机程序运行的一个实例。进程有很多东西可供他们使用。在每个程序开始时,您将获得一个进程,但每个程序可以创建更多进程。实际上,您的操作系统只启动了一个进程,所有其他进程都是分开的 - 所有这些都是在启动时在引擎盖下完成的。
进程是运行中的程序(大致)。进程也只是这个运行中的计算机程序的一个实例。进程有很多东西可供使用。在每个程序启动时,您将获得一个进程,但每个程序可以创建更多进程。实际上,您的操作系统只启动了一个进程,所有其他进程都是由它分叉的 - 所有这些都是在启动时在背后完成的。
## 在一开始的时候
## 最开始
当您的操作系统在 Linux 机器上启动时,会创建一个名为`init.d`的进程。该进程是处理信号,中断和某些内核元素的持久性模块的特殊进程。无论何时想要创建一个新进程,都可以调用`fork`(将在后面的部分中讨论)并使用另一个函数来加载另一个程序。
当您的操作系统在 Linux 机器上启动时,会创建一个名为`init.d`的进程。该进程是个处理信号和中断的特殊进程,和某些内核元素的持久性模块。无论何时想要创建一个新进程,都可以调用`fork`(将在后面的部分中讨论)并使用另一个函数来加载另一个程序。
## 进程隔离
进程非常强大,但它们是孤立的!这意味着默认情况下,任何进程都无法与另一个进程通信。这非常重要,因为如果你有一个大型系统(比方说 EWS),那么你希望某些进程拥有比普通用户更高的特权(监控,管理员),并且当然不希望普通用户能够带来通过修改进程有意或无意地整个系统。
进程非常强大,但它们是隔离的!这意味着默认情况下,任何进程都无法与另一个进程通信。这非常重要,因为如果你有一个大型系统(比方说 EWS),那么你希望某些进程拥有比普通用户更高的权限(监控,管理员),并且当然不希望普通用户通过有意或无意修改进程,能够搞崩整个系统。
如果我运行以下代码,
如果我在两个不同的终端上运行以下代码,
```
int secrets; //maybe defined in the kernel or else where
......@@ -22,7 +22,7 @@ secrets++;
printf("%d\n", secrets);
```
在两个不同的终端上,正如你猜测的那样,它们都打印出 1 而不是 2.即使我们改变了代码来做一些真正的 hacky(除了直接读取内存),也没有办法改变另一个进程的状态(好吧)也许[](https://en.wikipedia.org/wiki/Dirty_COW),但这有点太深入了)。
正如你猜测的那样,它们都打印出 1 而不是 2。即使我们改变了代码来做一些真正的黑魔法(除了直接读取内存),也没有办法改变另一个进程的状态(好吧,也许 [DirtyCow](https://en.wikipedia.org/wiki/Dirty_COW) 可以,但这有点太深入了)。
## 进程内容
......@@ -30,9 +30,9 @@ printf("%d\n", secrets);
![Address Space](img/70f6ba3a3d379fcd8d3214846a16c410.jpg)
当进程启动时,它会获得自己的地址空间。意味着每个进程得到(对于内存
当进程启动时,它会获得自己的地址空间。意味着每个进程得到
* **一个堆栈**。堆栈是存储自动变量和函数调用返回地址的位置。每次声明一个新变量时,程序都会向下移动堆栈指针以保留变量的空间。堆栈的这一部分是可写的但不可执行。如果堆栈增长得太远(意味着它增长超出预设边界或与堆相交),您将获得堆栈溢出,最有可能导致 SEGFAULT 或类似的东西。 **默认静态分配堆栈意味着只能写入一定数量的空间**
* **一个栈**。栈是存储自动变量和函数调用返回地址的位置。每次声明一个新变量时,程序都会向下移动栈指针来保留变量的空间。栈的这一部分是可写的但不可执行。如果栈增长得太远(意味着它增长超出预设边界或与堆相交),您将获得栈溢出,最有可能导致 SEGFAULT 或类似的东西。**栈默认静态分配,意味着只有一定数量的可写空间**
* **堆**。堆是一个扩展的内存区域。如果你想分配一个大对象,它就在这里。堆从文本段的顶部开始向上增长(有时当你调用`malloc`它要求操作系统向上推动堆边界时)。此区域也是可写但不可执行。如果系统受限制或者地址耗尽(在 32 位系统上更常见),则可能会耗尽堆内存。
* **数据段**包含所有全局变量。此部分从 Text 段的末尾开始,并且大小是静态的,因为在编译时已知全局变量。这部分是可写的但不是可执行的,这里没有其他任何东西太过花哨。
* **文本段**。可以说,这是地址中最重要的部分。这是存储所有代码的地方。由于汇编编译为 1 和 0,因此这是存储 1 和 0 的地方。程序计数器通过该段执行指令并向下移动下一条指令。请务必注意,这是代码中唯一的可执行部分。如果您尝试在运行时更改代码,很可能会出现段错误(有很多方法可以解决它但只是假设它是段错误的)。
......
......@@ -11,7 +11,7 @@
* 按 CTRL-C 时发送的信号
* 使用 shell 中的 kill 或 kill POSIX 调用。
* 进程内存隔离。
* 进程内存布局(堆,栈等;无效的内存地址)。
* 进程内存布局(堆,栈等;无效的内存地址)。
* 什么是叉炸弹,僵尸和孤儿?如何创建/删除它们。
* getpid 和 getppid
* 如何使用 WAIT 退出状态宏 WIFEXITED 等
......@@ -26,7 +26,7 @@
*`CTRL-C`时发送了什么信号?
* 我的终端锚定到 PID = 1337 并且刚刚变得没有响应。写下终端命令和 C 代码,将`SIGQUIT`发送给它。
* 一个进程可以通过正常方式改变另一个进程内存吗为什么?
* 堆,栈,数据和文本段在哪里?你能写些哪些细分?什么是无效的内存地址?
* 堆,栈,数据和文本段在哪里?你能写些哪些细分?什么是无效的内存地址?
* 用 C 编码叉炸弹(请不要运行它)。
* 什么是孤儿?它是如何变成僵尸的?我如何成为一个好父母?
* 当你父母告诉你不能做某事时,你不讨厌它吗?给我写一个程序,将`SIGSTOP`发送给你的父母。
......
......@@ -6,7 +6,7 @@
## 当我打电话给 malloc 时会发生什么?
函数`malloc`是 C 库调用,用于保留连续的内存块。与栈内存不同,内存保持分配状态,直到使用相同的指针调用`free`。还有`calloc``realloc`,将在下面讨论。
函数`malloc`是 C 库调用,用于保留连续的内存块。与栈内存不同,内存保持分配状态,直到使用相同的指针调用`free`。还有`calloc``realloc`,将在下面讨论。
## malloc 可以失败吗?
......@@ -16,13 +16,13 @@
堆是进程内存的一部分,它没有固定的大小。当您调用`malloc``calloc``realloc`)和`free`时,堆存储器分配由 C 库执行。
首先快速回顾一下进程内存:进程是程序的运行实例。每个进程都有自己的地址空间。例如,在 32 位机器上,您的进程可以获得大约 40 亿个地址,但并非所有这些地址都有效,甚至映射到实际物理内存(RAM)。在进程内存中,您将找到可执行代码,栈空间,环境变量,全局(静态)变量和堆。
首先快速回顾一下进程内存:进程是程序的运行实例。每个进程都有自己的地址空间。例如,在 32 位机器上,您的进程可以获得大约 40 亿个地址,但并非所有这些地址都有效,甚至映射到实际物理内存(RAM)。在进程内存中,您将找到可执行代码,栈空间,环境变量,全局(静态)变量和堆。
通过调用`sbrk`,C 库可以增加堆的大小,因为程序需要更多的堆内存。由于堆和堆栈(每个线程一个)需要增长,我们将它们放在地址空间的两端。因此,对于典型的体系结构,堆将向上生长并且堆栈向下增长。
通过调用`sbrk`,C 库可以增加堆的大小,因为程序需要更多的堆内存。由于堆和栈(每个线程一个)需要增长,我们将它们放在地址空间的两端。因此,对于典型的体系结构,堆将向上生长并且栈向下增长。
真实性:现代操作系统内存分配器不再需要`sbrk` - 相反,它们可以请求虚拟内存的独立区域并维护多个内存区域。例如,可以将千兆字节请求放置在与小分配请求不同的存储器区域中。然而,这个细节是一个不必要的复杂性:碎片化和有效分配内存的问题仍然适用,因此我们将在这里忽略这种实现,并将写入就像堆是单个区域一样。
如果我们编写一个多线程程序(稍后会详细介绍),我们将需要多个栈(每个线程一个),但只有一个堆。
如果我们编写一个多线程程序(稍后会详细介绍),我们将需要多个栈(每个线程一个),但只有一个堆。
在典型的体系结构中,堆是`Data segment`的一部分,并从代码和全局变量的上方开始。
......
# 内存,第 3 部分:粉碎栈示例
# 内存,第 3 部分:粉碎栈示例
> 原文:<https://github.com/angrave/SystemProgramming/wiki/Memory%2C-Part-3%3A-Smashing-the-Stack-Example>
每个线程使用堆栈内存。堆栈“向下增长” - 如果函数调用另一个函数,则堆栈扩展到较小的内存地址。堆栈内存包括非静态自动(临时)变量,参数值和返回地址。如果缓冲区太小某些数据(例如来自用户的输入值),那么很可能会覆盖其他堆栈变量甚至返回地址。堆栈内容的精确布局和自动变量的顺序取决于体系结构和编译器。然而,通过一些调查工作,我们可以学习如何故意粉碎特定架构的堆栈。
每个线程使用栈内存。栈“向下增长” - 如果函数调用另一个函数,则栈扩展到较小的内存地址。栈内存包括非静态自动(临时)变量,参数值和返回地址。如果缓冲区太小某些数据(例如来自用户的输入值),那么很可能会覆盖其他栈变量甚至返回地址。栈内容的精确布局和自动变量的顺序取决于体系结构和编译器。然而,通过一些调查工作,我们可以学习如何故意粉碎特定架构的栈。
下面的示例演示了返回地址如何存储在堆栈中。对于特定的 32 位架构 [Live Linux Machine](http://cs-education.github.io/sys/) ,我们确定返回地址存储在自动变量地址上方两个指针(8 个字节)的地址处。代码故意更改堆栈值,以便在输入函数返回时,而不是继续在 main 方法内部,它会跳转到 exploit 函数。
下面的示例演示了返回地址如何存储在栈中。对于特定的 32 位架构 [Live Linux Machine](http://cs-education.github.io/sys/) ,我们确定返回地址存储在自动变量地址上方两个指针(8 个字节)的地址处。代码故意更改栈值,以便在输入函数返回时,而不是继续在 main 方法内部,它会跳转到 exploit 函数。
```c
// Overwrites the return address on the following machine:
......
......@@ -6,7 +6,7 @@
## 什么是线程?
线程是“执行线程”的缩写。它表示 CPU 具有(并将执行)的指令序列。要记住如何从函数调用返回,并存储自动变量和参数的值,线程使用栈。
线程是“执行线程”的缩写。它表示 CPU 具有(并将执行)的指令序列。要记住如何从函数调用返回,并存储自动变量和参数的值,线程使用栈。
## 什么是轻量级过程(LWP)?它与线程有什么关系?
......@@ -16,13 +16,13 @@
LWP 或线程更倾向于分配许多场景,因为创建它们的开销要少得多。但在某些情况下(特别是 python 使用它)多处理是使代码更快的方法。
## 线程的栈如何工作?
## 线程的栈如何工作?
您的主要功能(以及您可能调用的其他功能)具有自动变量。我们将使用堆栈将它们存储在内存中,并使用简单的指针(“堆栈指针”)跟踪堆栈的大小。如果线程调用另一个函数,我们将堆栈指针向下移动,这样我们就有更多的空间用于参数和自动变量。一旦从函数返回,我们就可以将堆栈指针移回其先前的值。我们保留旧堆栈指针值的副本 - 在堆栈上!这就是为什么从函数返回非常快 - 很容易“释放”自动变量使用的内存 - 我们只需要更改堆栈指针。
您的主要功能(以及您可能调用的其他功能)具有自动变量。我们将使用栈将它们存储在内存中,并使用简单的指针(“栈指针”)跟踪栈的大小。如果线程调用另一个函数,我们将栈指针向下移动,这样我们就有更多的空间用于参数和自动变量。一旦从函数返回,我们就可以将栈指针移回其先前的值。我们保留旧栈指针值的副本 - 在栈上!这就是为什么从函数返回非常快 - 很容易“释放”自动变量使用的内存 - 我们只需要更改栈指针。
![](img/e209a7428a9ce45094abf36a151c7d63.jpg)
在多线程程序中,有多个堆栈但只有一个地址空间。 pthread 库分配一些堆栈空间(在堆中或使用主程序堆栈的一部分)并使用`clone`函数调用在该堆栈地址处启动线程。总地址空间可能看起来像这样。
在多线程程序中,有多个栈但只有一个地址空间。 pthread 库分配一些栈空间(在堆中或使用主程序栈的一部分)并使用`clone`函数调用在该栈地址处启动线程。总地址空间可能看起来像这样。
![](img/4013002f4b22a09d0fc6a117c0a29816.jpg)
......
......@@ -8,9 +8,9 @@
参见 [Pthreads 第 1 部分](https://github.com/angrave/SystemProgramming/wiki/Pthreads,-Part-1:-Introduction),其中介绍了`pthread_create``pthread_join`
## 如果我两次调用`pthread_create`,我的进程有多少栈?
## 如果我两次调用`pthread_create`,我的进程有多少栈?
您的进程将包含三个堆栈 - 每个线程一个堆栈。第一个线程是在进程启动时创建的,您还创建了另外两个。实际上可以有更多的堆栈,但是现在让我们忽略这种复杂性。重要的想法是每个线程都需要一个堆栈,因为堆栈包含自动变量和旧的 CPU PC 寄存器,因此它可以在函数完成后返回执行调用函数。
您的进程将包含三个栈 - 每个线程一个栈。第一个线程是在进程启动时创建的,您还创建了另外两个。实际上可以有更多的栈,但是现在让我们忽略这种复杂性。重要的想法是每个线程都需要一个栈,因为栈包含自动变量和旧的 CPU PC 寄存器,因此它可以在函数完成后返回执行调用函数。
## 完整进程和线程之间有什么区别?
......@@ -95,7 +95,7 @@ int main() {
## 你可以将指针从一个线程传递到另一个线程吗?
是。但是,您需要非常小心栈变量的生命周期。
是。但是,您需要非常小心栈变量的生命周期。
```
pthread_t start_threads() {
......@@ -108,7 +108,7 @@ pthread_t start_threads() {
上面的代码无效,因为函数`start_threads`可能会在`myfunc`开始之前返回。该函数传递`start`的地址,但是在`myfunc`执行时,`start`不再在范围内,其地址将重新用于另一个变量。
以下代码有效,因为栈变量的生命周期比后台线程长。
以下代码有效,因为栈变量的生命周期比后台线程长。
```
void start_threads() {
......@@ -182,7 +182,7 @@ char *to_message(int num) {
}
```
在上面的代码中,结果缓冲区存储在全局内存中。这很好 - 我们不希望返回指向栈上无效地址的指针,但整个内存中只有一个结果缓冲区。如果两个线程同时使用它,那么一个会破坏另一个:
在上面的代码中,结果缓冲区存储在全局内存中。这很好 - 我们不希望返回指向栈上无效地址的指针,但整个内存中只有一个结果缓冲区。如果两个线程同时使用它,那么一个会破坏另一个:
| 时间 | 线程 1 | 线程 2 | 评论 |
| --- | --- | --- | --- |
......
......@@ -5,7 +5,7 @@
## 话题
* pthread 生命周期
* 每个线程都有一个
* 每个线程都有一个栈
* 从线程捕获返回值
* 使用`pthread_join`
* 使用`pthread_create`
......@@ -15,9 +15,9 @@
## 问题
* 创建 pthread 会发生什么? (你不需要进入超级细节)
* 每个线程的栈在哪里?
* 每个线程的栈在哪里?
* 如果给出`pthread_t`,你如何得到一个回报值?线程可以设置返回值的方式是什么?如果您丢弃返回值会发生什么?
* 为什么`pthread_join`很重要(想想栈空间,寄存器,返回值)?
* 为什么`pthread_join`很重要(想想栈空间,寄存器,返回值)?
* 在正常情况下`pthread_exit`会做什么(即你不是最后一个线程)?调用 pthread_exit 时会调用哪些其他函数?
* 给我三个条件,在这个条件下多线程进程将退出。你还能想到吗?
* 什么是令人尴尬的并行问题?
\ No newline at end of file
......@@ -2,7 +2,7 @@
> 原文:<https://github.com/angrave/SystemProgramming/wiki/Synchronization%2C-Part-3%3A-Working-with-Mutexes-And-Semaphores>
## 线程安全
## 线程安全栈
## 什么是原子操作?
......@@ -14,7 +14,7 @@
## 如何使用互斥锁使我的数据结构线程安全?
注意,这只是一个介绍 - 编写高性能的线程安全数据结构需要它自己的书!这是一个非线程安全的简单数据结构(栈):
注意,这只是一个介绍 - 编写高性能的线程安全数据结构需要它自己的书!这是一个非线程安全的简单数据结构(栈):
```c
// A simple fixed-sized stack (version 1)
......@@ -35,11 +35,11 @@ int is_empty() {
}
```
堆栈的版本 1 不是线程安全的,因为如果两个线程同时调用 push 或 pop,则结果或堆栈可能不一致。例如,假设两个线程同时调用 pop,则两个线程可以读取相同的值,两者都可以读取原始计数值。
栈的版本 1 不是线程安全的,因为如果两个线程同时调用 push 或 pop,则结果或栈可能不一致。例如,假设两个线程同时调用 pop,则两个线程可以读取相同的值,两者都可以读取原始计数值。
为了将其转换为线程安全的数据结构,我们需要识别代码的 _ 关键部分 _,即代码的哪个部分一次只能有一个线程。在上面的例子中,`push``pop``is_empty`函数访问相同的变量(即存储器)和栈的所有关键部分。
为了将其转换为线程安全的数据结构,我们需要识别代码的 _ 关键部分 _,即代码的哪个部分一次只能有一个线程。在上面的例子中,`push``pop``is_empty`函数访问相同的变量(即存储器)和栈的所有关键部分。
`push`(和`pop`)正在执行时,数据结构是不一致的状态(例如,计数可能尚未写入,因此可能仍包含原始值)。通过使用互斥锁包装这些方法,我们可以确保一次只有一个线程可以更新(或读取)栈。
`push`(和`pop`)正在执行时,数据结构是不一致的状态(例如,计数可能尚未写入,因此可能仍包含原始值)。通过使用互斥锁包装这些方法,我们可以确保一次只有一个线程可以更新(或读取)栈。
候选“解决方案”如下所示。这是对的吗?如果没有,它将如何失败?
......@@ -76,7 +76,7 @@ int is_empty() {
上面的代码('版本 2')包含至少一个错误。花点时间看看你是否可以得到错误并找出后果。
如果三个同时调用`push()`,则`m1`确保只有一个线程处理栈(两个线程需要等到第一个线程完成(调用解锁)),然后第二个线程将被允许继续进入临界区,最后第二个线程完成后,第三个线程将被允许继续。
如果三个同时调用`push()`,则`m1`确保只有一个线程处理栈(两个线程需要等到第一个线程完成(调用解锁)),然后第二个线程将被允许继续进入临界区,最后第二个线程完成后,第三个线程将被允许继续。
类似的参数适用于`pop`的并发调用(同时调用)。但是,版本 2 不会阻止 push 和 pop 同时运行,因为`push``pop`使用两个不同的互斥锁。
......@@ -113,12 +113,12 @@ int is_empty() {
版本 3 是线程安全的(我们已确保所有关键部分的互斥)但是有两点需要注意:
* `is_empty`是线程安全的,但其结果可能已经过时,即在线程获得结果时栈可能不再为空!
* 没有防止下溢(在空堆栈上弹出)或溢出(推入已经满的堆栈)的保护
* `is_empty`是线程安全的,但其结果可能已经过时,即在线程获得结果时栈可能不再为空!
* 没有防止下溢(在空栈上弹出)或溢出(推入已经满的栈)的保护
后一点可以使用计数信号量来修复。
该实现假设一个栈。更通用的版本可能包含互斥锁作为内存结构的一部分,并使用 pthread_mutex_init 初始化互斥锁。例如,
该实现假设一个栈。更通用的版本可能包含互斥锁作为内存结构的一部分,并使用 pthread_mutex_init 初始化互斥锁。例如,
```c
// Support for multiple stacks (each one has a mutex)
......@@ -175,11 +175,11 @@ int main() {
}
```
## 栈信号量
## 栈信号量
## 如果栈为空或已满,我如何强制我的线程等待?
## 如果栈为空或已满,我如何强制我的线程等待?
使用计数信号量!使用计数信号量来跟踪剩余的空格数和另一个信号量来跟踪栈中的项目数。我们将这两个信号量称为“sremain”和“sitems”。记住,如果信号量的计数已减少到零(由另一个调用 sem_post 的线程),`sem_wait`将等待。
使用计数信号量!使用计数信号量来跟踪剩余的空格数和另一个信号量来跟踪栈中的项目数。我们将这两个信号量称为“sremain”和“sitems”。记住,如果信号量的计数已减少到零(由另一个调用 sem_post 的线程),`sem_wait`将等待。
```c
// Sketch #1
......@@ -202,7 +202,7 @@ void push(double v) {
...
```
草图#2 过早地实现了`post`。另一个在推送中等待的线程可能会错误地尝试写入完整栈(类似地,在 pop()中等待的线程被允许过早地继续)。
草图#2 过早地实现了`post`。另一个在推送中等待的线程可能会错误地尝试写入完整栈(类似地,在 pop()中等待的线程被允许过早地继续)。
```c
// Sketch #2 (Error!)
......
......@@ -89,7 +89,7 @@ if(not_ready){
* 什么是线程障碍?
* 使用计数信号量来实现屏障。
* 编写生产者/消费者队列,生产者消费者栈怎么样?
* 编写生产者/消费者队列,生产者消费者栈怎么样?
* 给我一个带条件变量的读写器锁的实现,用你需要的任何东西制作一个结构,它只需要能够支持以下函数
......
......@@ -69,7 +69,7 @@ VirtualAddress = 11110000111111110000000010101010 (binary)
|__________| 12 bit offset (passed directly to RAM)
```
在上述方案中,确定帧号需要两次存储器读取:最顶层的 10 位用于页表的目录中。如果每个条目使用 2 个字节,我们只需要 2KB 来存储整个目录。每个子表将指向物理帧(即,需要 4 个字节来存储 20 位)。但是,对于只有微小内存需求的进程,我们只需要为低内存地址(用于堆和程序代码)和高内存地址(用于栈)指定条目。每个子表是 1024 个条目×4 个字节,即每个子表 4KB。因此,我们的多级页表的总内存开销从 4MB(单级)缩减到 3 帧内存(12KB)!
在上述方案中,确定帧号需要两次存储器读取:最顶层的 10 位用于页表的目录中。如果每个条目使用 2 个字节,我们只需要 2KB 来存储整个目录。每个子表将指向物理帧(即,需要 4 个字节来存储 20 位)。但是,对于只有微小内存需求的进程,我们只需要为低内存地址(用于堆和程序代码)和高内存地址(用于栈)指定条目。每个子表是 1024 个条目×4 个字节,即每个子表 4KB。因此,我们的多级页表的总内存开销从 4MB(单级)缩减到 3 帧内存(12KB)!
页表会使内存访问速度变慢吗? (什么是 TLB)
......@@ -117,7 +117,7 @@ VirtualAddress = 11110000111111110000000010101010 (binary)
### 执行位
执行位定义页面中的字节是否可以作为 CPU 指令执行。通过禁用页面,它可以防止恶意存储在进程存储器中的代码(例如,通过栈溢出)被轻易执行。 (进一步阅读: [http://en.wikipedia.org/wiki/NX_bit#Hardware_background](http://en.wikipedia.org/wiki/NX_bit#Hardware_background)
执行位定义页面中的字节是否可以作为 CPU 指令执行。通过禁用页面,它可以防止恶意存储在进程存储器中的代码(例如,通过栈溢出)被轻易执行。 (进一步阅读: [http://en.wikipedia.org/wiki/NX_bit#Hardware_background](http://en.wikipedia.org/wiki/NX_bit#Hardware_background)
### 了解更多
......
......@@ -228,7 +228,7 @@ printf("%p : %s\n", ptr, ptr);
strcpy(ptr, "World"); // OK because now ptr is pointing to mutable memory (the array)
```
从中可以看出,指针*可以指向任何类型的内存,而 C 数组[]只能指向栈上的内存。在更常见的情况下,指针将指向堆内存,在这种情况下,指针 CAN 引用的内存可以被修改。
从中可以看出,指针*可以指向任何类型的内存,而 C 数组[]只能指向栈上的内存。在更常见的情况下,指针将指向堆内存,在这种情况下,指针 CAN 引用的内存可以被修改。
### `sizeof()`返回字节数。所以使用上面的代码,sizeof(ary)和 sizeof(ptr)是什么?
......@@ -261,7 +261,7 @@ char* f2() {
} // Incorrect!
```
说明:在堆栈上创建一个数组 p,其大小正确以容纳 H,e,l,l,o 和空字节,即(6)字节。这个数组存储在堆栈中,从 f2 返回后无效。
说明:在栈上创建一个数组 p,其大小正确以容纳 H,e,l,l,o 和空字节,即(6)字节。这个数组存储在栈中,从 f2 返回后无效。
```c
char* f3() {
......@@ -279,7 +279,7 @@ char* f4() {
} // OK
```
说明:数组是静态的,意味着它在进程的生命周期中存在(静态变量不在堆或栈上)。
说明:数组是静态的,意味着它在进程的生命周期中存在(静态变量不在堆或栈上)。
### 你如何查询 C 库调用和系统调用的信息?
......
......@@ -92,7 +92,7 @@ typedef ______________________;
## 问 1.7
除了函数参数之外还有哪些东西存储在线程的栈中?
除了函数参数之外还有哪些东西存储在线程的栈中?
## 问题 1.8
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册