lecture4

发布时间 2023-06-23 18:05:15作者: viewoverlook

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的地址。

指针可视化如下:
pointer

简化如下:
pointer

Strings

我们已经有了指针的思维模型,现在我们剥开在课堂早期对string提供的封装,这在c标准库中并不存在。

对于一个简单的字符串,例如string s = "HI!"可以被表示如下:
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!',但是输出的结果是不同的
不同的原因借助图表示如下
two strings

因此字符串比较应该使用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]的值。

two strings

这并不是我们所期望的,我们期望的是将s复制到t中,这样t和s指向不同的地址,改变t[0]不会影响s[0]的值。

为了可以实现这个副本,我们会介绍两个新的构建块。

  1. malloc函数,用来分配内存
  2. 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

  • 堆溢出:当我们分配的内存超过了我们内存大小,就会发生堆溢出
  • 栈溢出:当我们的函数调用层次太深,超过了栈的大小,就会发生栈溢出
    上述所有的溢出都统称为缓冲区溢出