/ CS50

CS50 筆記:第四講 Memory

筆記前言

覺得這章是課程中我覺得蠻有趣的一章,除了學到了很多我以前不會的東西以外,這章的 problem set 很有趣,會帶著大家做一些基本的圖片處理(灰階、模糊、反轉、探測邊緣)。


記憶體就像下圖的格子一樣有一格又一格的空間,我們可以幫每個 byte 編號。通常在電腦科學裡面會用十六進制來編號。

memory grid

十六進制

遇到 15 進位,十進制中的 10, 11, 12, 13, 14, 15 分別用 A, B, C, D, E, F 來表示。

十六進制的數數就會像底下這樣 00 01 02 03 04 05 06 07 08 09 0A (還沒進位!) 0B 0C 0D 0E 0F 10 …

所以兩位數的時候最多可以算到 FF (十進制的 255)

正好十六進制 FF 相當於二進制的 11111111(8bits!),每個十六進制的位數都可以表示 4 個 bits,正好可以用 2 個為表示一個 bytes

1111 1111 => FF

Hex Color Codes

常看到的網頁色碼 #f0f8ff, #db90b5,就是用分別用 1 個 byte來表示紅綠藍三種顏色的光的量,來表示一個顏色。

hex color

pointer

  • & address of: 這個變數在哪個記憶體位置

  • *: 這個記憶體位置存的是誰

string

一個 string 是一個 char 組成的陣列,之前學過透過 [] 來取得各個元素,例如 s[1], s[2], s[3]。因為陣列是連續的記憶體空間,每個元素會正好相鄰(char 佔一個 byte,因此每個元素的位置都隔一個 byte)

#include <stdio.h>

int main(void)
{
    char *s = "HI!";
    printf("%c\n", s[0]); // 例如 0x125
    printf("%c\n", s[1]); // 0x126
    printf("%c\n", s[2]); // 0x127
}

於是,除了 s[1] ,我們可以透過指標的方式 *(s+1) 來獲得下一個元素的內容

#include <stdio.h>

int main(void)
{
    char *s = "HI!";
    printf("%c\n", *s);
    printf("%c\n", *(s+1));
    printf("%c\n", *(s+2));
}

segmentation fault

當我們嘗試去操作不該碰的 memory ,程式會發生 segmentation fault。

compare & copy

比較兩個 *char 其實是在比較記憶體位置,即便 value 一樣也會被判斷成是不同的(str1 == str2 為 false)

valgrind

如果自行 malloc 要記得 free,忘記的話就會造成 memory leak,這些被忘記 free 的記憶體無法再被其他程式使用。

這種時候就可以使用 valgrind ,可以用來檢查程式是否有造成 memory leak 的工具,在 CS50 IDE 裡面預設有裝。

比方說當前目錄有個叫 memory 的執行檔,可以跑 valgrind ./memory 看他的分析

作用域

教授寫了一個 function 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;
}

但最後印出來的結果會是

x is 1, y is 2
x is 1, y is 2

實際上執行會是,在主程式裡面的時候在 stack 裡放上 x 跟 y 的值。到 swap 裡時再放上 a 跟 b 的值,如下圖。 a 和 b 交換完以後,記憶體被釋放(a 跟 b 放的 1 跟 2 變成 garbage value),回到主程式。於是 x 跟 y 都保持原樣。

memory stack

所以要在另一個 function 中交換 x, y,就必須透過指標。

#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;
}

scanf

之前都是用 cs50.h 的 get_string 等等,助教講師群包好的工具。實際上要自己獲得使用者的 output 的話,可以用 scanf。

get_string 方便的點是他把 linked list 之類的部分處理好了,可以接任意長度的用戶 input,但 scanf 就要看你自己拿來接的 char *的長度。

char * 再自己 malloc 和陣列可以做到差不多的效果,差別在於陣列用 memory 中的 stack,系統會幫你處理好不會 memory leak,malloc 則是用 heap,要記得 free 否則會造成 memory leak

fopen, fprintf, fclose

記憶體裡的內容會隨著電源關閉而消失,如果想要長久保存資料,就必須存在印碟裡,把資料寫入檔案中。

教授透過電話簿的範例,來示範檔案相關的操作

讀檔

FILE *file = fopen("phonebook.csv", "a");

寫入

fprintf(file, "%s,%s\n", name, number);

關閉檔案

fclose(file);

jpeg

可以用前幾個 bytes 就判斷一個檔案是否為 jpeg(被稱為 magic number)。因為這些檔案有特定的 standard,可以快速分辨它是什麼檔案

  • 先用 fread 讀檔
  • jpeg 前三個 bytes 會是: 0xff 0xd8 0xff

quiz

1) In your own words, what is a pointer?

我自己的答案:在 C 裡面的一種 data type,用來存放記憶體位置。在 C 裡面,我們可以透過指標去取出他指向的值(dereferencing)。

2) If s is of type string, in what sense is s a pointer?

我自己的答案:在 c 當中不存在 string 這個型別,實際上他是 char * (只是在 cs50.h 裡把它定義成叫做 string 的 type。 char * 代表他是個指標,指向的 value 應該被當成 char 來解讀。

3) If s and t are of type string, why can we not use s == t to check whether s and t contain the same characters?

我自己的答案:當 s 跟 t 都是 char *,在做 s == t 這件事情實際上的意思是在比較兩個記憶體位置,即便 t 指向的位置存的內容跟 s 指向的位置存的內容一樣,兩個記憶體位置有可能是不一樣的,所以無法用這種方式去比較 s 和 t 是否有相同的字串。

fumitsuki

文月

擅長耍雷的フレンズ,在茫茫海海探索人生中