Lecture4 Memory
Addressres
C语言提供了了两种关于内存的强大操作
& // 提供在内存中所存事物的地址
* // 指示编译器去往内存中某个位置
example:
#include<stdio.h>
int main(void)
{
int n = 50;
printf("%p\n", &n);
}
%p
是一个格式化字符串,用来打印地址,&n
是一个地址.
Pointers
指针式包含某个值的地址的变量,更简洁的说,指针式你计算机内存中的地址。
#include <stdio.h>
int main(void)
{
int n = 50;
int *p = &n;
printf("%p\n", p);
}
指针p包含了变量n的地址。
指针可视化如下:
简化如下:
Strings
我们已经有了指针的思维模型,现在我们剥开在课堂早期对string
提供的封装,这在c标准库中并不存在。
对于一个简单的字符串,例如string s = "HI!"
可以被表示如下:
s
指针告诉编译器字符串的第一个字符的地址
字符串比较
一个字符串仅仅是字符数组由其第一个字节标识
在上一周的课程中,我们指出不能'=='直接比较字符串,因为这样比较的是地址,而不是字符串的内容。
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Get two strings
char *s = get_string("s: ");
char *t = get_string("t: ");
// Compare strings' addresses
if (s == t)
{
printf("Same\n");
}
else
{
printf("Different\n");
}
}
在这里会发现输入同样的'HI!',但是输出的结果是不同的
不同的原因借助图表示如下
因此字符串比较应该使用strcmp
函数
copying
字符串常用操作是将一个字符串复制到另一个字符串中
先看一段程序
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// Get a string
string s = get_string("s: ");
// Copy string's address
string t = s;
// Capitalize first letter in string
t[0] = toupper(t[0]);
// Print string twice
printf("s: %s\n", s);
printf("t: %s\n", t);
}
这里的string t = s
并不是将字符串s复制到t中,而是将t指向s的地址,因此t和s指向同一个地址,因此t[0]改变了s[0]的值。
这并不是我们所期望的,我们期望的是将s复制到t中,这样t和s指向不同的地址,改变t[0]不会影响s[0]的值。
为了可以实现这个副本,我们会介绍两个新的构建块。
malloc
函数,用来分配内存free
函数,用来释放内存
将上段代码修改如下:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
// Get a string
char *s = get_string("s: ");
// Allocate memory for another string
char *t = malloc(strlen(s) + 1);
// Copy string into memory, including '\0'
for (int i = 0; i <= strlen(s); i++)
{
t[i] = s[i];
}
// Capitalize copy
t[0] = toupper(t[0]);
// Print strings
printf("s: %s\n", s);
printf("t: %s\n", t);
}
malloc(strlen(s)+1)
为字符串s分配了内存,strlen(s)
是字符串s的长度,+1
是为了存储字符串结束符\0
。
之后对于字符串的复制,我们需要一个循环,因为字符串是一个字符数组,我们需要一个一个的复制。
C语言之中也有一个内置函数strcpy
,可以实现字符串的复制,但是这个函数需要我们提供目标字符串的地址,因此我们需要使用malloc
函数。
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
// Get a string
char *s = get_string("s: ");
// Allocate memory for another string
char *t = malloc(strlen(s) + 1);
// Copy string into memory
strcpy(t, s);
// Capitalize copy
t[0] = toupper(t[0]);
// Print strings
printf("s: %s\n", s);
printf("t: %s\n", t);
}
在 get_string
,malloc
都会返回NULL,如果我们不检查这个返回值,会导致程序崩溃。验证程序分配内存成功如下
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
// Get a string
char *s = get_string("s: ");
if (s == NULL)
{
return 1;
}
// Allocate memory for another string
char *t = malloc(strlen(s) + 1);
if (t == NULL)
{
return 1;
}
// Copy string into memory
strcpy(t, s);
// Capitalize copy
if (strlen(t) > 0)
{
t[0] = toupper(t[0]);
}
// Print strings
printf("s: %s\n", s);
printf("t: %s\n", t);
// Free memory
free(t);
return 0;
}
valgrind
valgrind 是一个检查内存是否及时释放的工具
使用方法:
valgrind ./test
Garbage Values
在C语言中,如果我们没有初始化一个变量,那么这个变量的值是不确定的,这个值可能是之前这个内存地址的值,也可能是其他的值,这个值称为垃圾值。
swap
很难在不借助中间值的情况下,交换两个变量的值,但是可以借助指针来实现。
先来看一段错误程序
#include <stdio.h>
void swap(int a, int b);
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i, y is %i\n", x, y);
swap(x, y);
printf("x is %i, y is %i\n", x, y);
}
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
这段程序并不能实现目的,在普通的函数中,参数是值传递,是将参数副本传到函数中,因此在函数中修改参数的值,并不会影响到函数外的变量。
修正代码如下:
#include <stdio.h>
void swap(int *a, int *b);
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i, y is %i\n", x, y);
swap(&x, &y);
printf("x is %i, y is %i\n", x, y);
}
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
overflow
- 堆溢出:当我们分配的内存超过了我们内存大小,就会发生堆溢出
- 栈溢出:当我们的函数调用层次太深,超过了栈的大小,就会发生栈溢出
上述所有的溢出都统称为缓冲区溢出