【UCB CS61C】Lecture 2 3 - C Basics

2024-08-24 03:12
文章标签 lecture basics ucb cs61c

本文主要是介绍【UCB CS61C】Lecture 2 3 - C Basics,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • C 语言的编译(Compilation)
  • 变量类型(Variable Types)
    • 字符(Characters)
  • C 语言的类型转换(Typecasting)
    • 类型函数(Typed Functions)
  • 结构体(Structs)
    • 成员的对齐与填充(Alignment & Padding)
    • 节省结构体对齐填充的空间
  • 联合体(Unions)
  • `main()` 函数
  • True or False?
  • 指针(Pointers)
    • 地址和值
    • 参数传递
    • 为什么使用指针?
    • 指向不同大小的对象
  • `sizeof()` 运算符
  • 数组(Array)
  • 字符串(Strings)
  • 指针运算
  • 指向指针的指针(Pointers to Pointers)

本文章系计算机体系结构课程 UCB CS61C: Great Ideas in Computer Architecture 的学习笔记。

Overview

C 语言的编译(Compilation)

  • C 语言是一种编译型语言
  • C 编译器将 C 程序映射到面向架构architecture-specific)的机器码中(实际上是一串 0 和 1 的组合)。
    • 不同于Java ,Java 会通过 JVM 虚拟机将代码转换为独立于架构(architecture-independent)的字节码(bytecode)。
    • 不同于 Python,Python 直接解释(interpret)代码,C 只是将代码编译为机器码,从而由 CPU 直接解释并运行。

编译的优势如下:

  • 出色的运行性能run-time performance
  • 合理的编译时间——编译过程(Makefiles)的增强允许我们只重新编译修改过的文件

然而,编译也有劣势:

  • 编译后的文件——包括对应的可执行文件——是特定于某一体系结构的,平台之间的移植需要进行一定程度上的重构。
  • 基于编译的循环「 编辑 ⇒ 编译 ⇒ 运行 ⇒ 【重复】」可能会较慢。

变量类型(Variable Types)

  • 整型(intintegers)的大小取决于机器! 常见的大小是 4 或 8 字节(32/64 位)。

字符(Characters)

  • 将字符编码为数字,ACSII 标准定义了128 个不同字符以及对应的数字。
  • 一个 char 类型的变量占用 1 个字节的空间。
  • 7 位(bits,比特)的空间足以存储一个字符(27= 128),但是通常会预留一个字符的空间用来处理多个字节(bytes)。

C 语言的类型转换(Typecasting)

  • C 是一种弱类型语言,这意味着可以显式地从任何类型进行类型转换。

类型函数(Typed Functions)

  • 我们可以定义函数之前,在头文件(header files)声明一个函数的原型prototypes,例如 int fun(int, int); ),以便其他程序可在不考虑其他已定义的变量的情况下调用该函数作为库(library)。

结构体(Structs)

  • 一种定义复合数据类型的方法
typedef struct {int lengthInSeconds;int yearRecords;
} Song;Song tearsInHeaven;tearsInHeaven.lengthInSeconds = 285;
tearsInHeaven.yearRecords = 1992;

成员的对齐与填充(Alignment & Padding)

  • 为了提高内存访问效率而采取的一种策略
  • 对齐:数据在内存中存储时,必须按照一定的规则进行排列。通常,数据类型的大小和处理器的字长决定了对齐的要求(例如,一个 32 位的处理器可能要求 32 位的数据类型 int 必须存储在 4 字节 32 位的边界上)。
  • 结构成员按其声明顺序进行存储:第一个成员的内存地址最低,最后一个成员的内存地址最高。
  • 填充:在结构体成员之间或结构体的末尾添加一些额外的字节,以确保每个成员都能对齐到相应的类型边界。填充字节不存储任何有意义的数据,它们的存在仅仅是为了满足对齐要求。值得注意的是,我们必须保证每一个变量的填充均拥有与自身大小一致的边界(如果有其他变量占用的话,例如 int 类型,它对应地址的左右边界应至少包含 4 个字节,若其中任一边界无其他变量的占用,则那一边界无需占用额外空间)。

例如,我们在 32 位架构上定义一个结构体 foo

struct foo {int a;char b;struct foo *c;
};

这一结构体的占用情况如下:

  • a :4 字节
  • b :1 字节
  • 3 字节的闲置空间
  • *c :4 字节(32 位架构的指针占用 32 位即 4 字节)
  • sizeof(struct foo) == 12

节省结构体对齐填充的空间

假设我们有如下结构体定义:

struct hello {int a;char b;short c;char *d;char e;
};

如果我们执行 sizeof(struct hello); 这一语句,得到的结果将是 16
struct memory
为了进一步节省空间,我们可以将字符 c 移到 pad c 的位置( ⚠️ 结构体的成员变量是有存储顺序的!):
Improvement

struct hello {int a;char b;char e;   // e 移到这里short c;char *d;
};

这样,结构体的大小就缩减为 12 了。随着结构体存储数据的逐渐增多,我们可以通过调整成员变量的先后顺序来优化内存空间的管理。

联合体(Unions)

  • 类似于结构体,但联合体只为单个最大元素存储足够的数据,映射所有成员的数据到同一空间,从而节省内存空间,并有助于解释机器码。
union Example {int i;float f;char str[20];
};union Example u;
u.i = 10;
u.f = 3.14;
strcpy(u.str, "Hello");   // including <string.h>

main() 函数

函数签名为 int main(int argc, char *argv[]);

  • argcargument count)- 包含命令行参数的个数(argc 至少为 1,即执行程序的路径会加入计数,每拥有一个参数就会增加 1)
  • argvargument value)- 一个包含指向参数的字符串形式指针的数组,argv 的第一个元素( argv[0] )是程序的名称,后续的元素( argv[1]argv[argc - 1] )是传递给程序的参数;值得注意的是,argv 实际上总是以 null point 结尾,所以 argv[argc] 通常为 0。

True or False?

  • C 没有显式的布尔(Boolean)变量类型,对应地,C 将 false 评估为 0 或者 NULL ,而所有非 false 的变量即为 true

指针(Pointers)

地址和值

  • 内存相当于一个单链的、巨大的数组:
    • 每一个数组的单元均有一个地址
    • 每一个数组的单元均存储一些值
  • 指针是包含一个内存地址的变量,它指向内存的某个位置。
int x = 3;
int y = 4;int *p;  //定义一个指针
p = &x;  //令 p 指向 x
*p = 5;  //修改 x 的值为 5
y = *p;  //修改 y 的值为 5

参数传递

  • C 按值传递参数,函数会获得一个关于参数的拷贝,因此在函数内部更改参数并不会修改外部传入的参数本身。
  • 可以通过按引用(reference)传递参数来更改参数本身,函数接受指针,通过解引用(dereference)来修改值
void addOne (int *p) {*p += 1;
}int y = 3;
addOne(&y);

为什么使用指针?

  • 当传递一个巨大的结构体或数组时,传递一个指针远比传递整个数据要更容易、更快速。
  • 指针允许使用更简洁的代码。

❗️请小心使用指针,它是 C 语言 bug 的最大单一来源,不正确的处理会导致内存泄漏。
在这里插入图片描述

指向不同大小的对象

  • 现代机器是按字节寻址的,C 指针只是一个抽象的内存指针。
  • 类型定义告知编译器每次通过指针访问地址时需要抓取多少字节。

sizeof() 运算符

  • 返回一个变量或数据类型的大小(以字节为单位)。
int x;
int *y;
sizeof(x);      // 4 (32-bit int)
sizeof(int);    // 4 (32-bit int)
sizeof(y);      // 4 (32-bit addr)
sizeof(char);   // 1 (32-bit char)
  • sizeof() 对于数组和结构体的处理也是不一样的:
    • 对于数组,返回整个数组的长度。
    • 对于结构体,返回其中一个实例(instance)的大小(所有结构体成员以及闲置空间的大小之和)。

数组(Array)

  • C 数组并不知道自身的长度,也不会检查索引范围是否溢出。因此,我们必须在传递数组的同时将其长度也一并传递
  • 数组范围的错误会导致不规范的内存访问与写入,从而引发分段错误(Segmentation faults,软件错误,当程序试图访问其无权访问的内存位置时发生)和总线错误(Bus errors,硬件错误,通常发生在程序试图访问无效的内存地址或未对齐的内存地址时),很难查找。
    usage
  • 数组类似于指针,看起来像是指向第一个元素的常量指针。
    • ar[0] 等价于 *arar[2] 等价于 *(ar + 2)
  • 数组名称并不属于变量,因此求其地址是没有意义的
  • 不要返回在函数内部定义的局部数组变量存储的内存地址,局部变量会在函数执行完成后被销毁。

字符串(Strings)

  • C 字符串只是一个字符数组,包含终止符 '\0' ,请在 char[] 类型中正确放置空终止符 '\0'
char letters[] = "abc";
const char letters[] = {'a', 'b', 'c', '\0'};

我们可以使用一个简单的函数 strlen() 来得到字符串的长度:

int strlen(char s[]) {int n = 0;while (s[n] != 0) {n++;}return n;
}
  • 如果我们引用 <string.h> 库,有内置的函数可供使用:
    • int strlen(char *string) :返回字符串的长度。
    • int strcmp(char *str1, char *str2) :如果 str1str2 相同,返回 0 ;若不相同,则返回 str1str2 第一个不匹配的字符间的 ACSII 差值。这与 str1 == str2 不同,后者是在检查两者指向的内存地址是否一致。
    • char *strcpy(char *dist, char *src) :将字符串 src内容拷贝至目标内存地址,目标地址需要保证有足够的空间(实际长度 + 1)。

指针运算

  • 当一个指针加上(或减去)一个整数时,指针向前(向后)移动若干个元素,实际上是改变指针指向的地址从而访问不同元素。移动的元素个数等于整数值乘以指针所指向类型的大小
  • 合法的指针运算有:
    • 对一个指针加上一个整数
    • 在同一数组中,对两个指针求差
    • 比较两个指针
    • 比较一个指针与 NULL (表示指向空地址)
  • 不合法也不合理的指针运算有:
    • 两个指针相加
    • 两个指针求积
    • 从整数减去一个指针

在这里插入图片描述

  • 当存在多个前缀运算符时,它们从右到左应用:
    • *--p :先使指针 p 递减指向相邻的地址,然后解引用读取该地址对应的值。
    • ++*p :先解引用读取指针 p 指向的地址对应值,然后使该值立刻自增。
  • 对于后缀运算符,它优先于前缀运算符,但将最后生效
    • *p++ :先解引用指针 p 指向的值,再递增指针 p 移到下一个元素。
    • (*p)++ :先解引用指针 p 指向的值,再使该值稍后自增。

指向指针的指针(Pointers to Pointers)

  • 函数要想修改指针(使指针指向不同地址),可以传入形参 **h ,再对指针的指针解引用还原为目标指针 p
void IncrementPtr(int **h) {*h = *h + 1;
}int A[3] = {50, 60, 70};int *q = A;IncrementPtr(&q);

doule p

神尾观铃

这篇关于【UCB CS61C】Lecture 2 3 - C Basics的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1101288

相关文章

8月26日cs61c

小语 别人恐惧我贪婪,别人贪婪我恐惧。——巴菲特 1.啃黑书10页 10.05P25 1.电信号的发送:最简单的信号是“通” 和“断".代表两个字母的符号是0和1 2.例如,位串 1000110010100000 告诉计算机将2个数相加。 3.重点 组成计算机的5个经典部件是输入、输出、存储器、 数据通路(在计算机中也称运 算器)和控制器,其中最后两个部件通常合称为处理器。 图 1-5

ffmpeg学习二:《FFmpeg Basics》读书笔记(上)

为了更好的理解ffmpeg工程,官方推荐了一本书:《FFmpeg Basics》。完整的读完这本书,应该对这个工程能有一个基本的理解了。本菜英文不好,姑且从这本书中提炼出一些比较常用的知识,做个笔记吧。 一.波特率,帧率,和文件大小 1.1帧率 1-1-1帧率的基本概念 帧率,英文说就是frames per second(FPS or fps),也就是每秒钟的帧数,完整的说就是每秒钟编码到

MEMS:Lecture 18 Feedback

讲义 Linear feedback MEMS热板 Hotplate MEMS(微机电系统)热板是现代气体传感器的重要组成部分。它们通过加热一种活性材料来工作,这种材料与气体发生反应,从而改变其电阻。电阻的变化可以用来检测和测量特定气体的存在和浓度。 MEMS热板通常由以下几个部分组成: 加热元件:通常是由薄金属膜(如钛/钛氮化物)构成的螺旋形加热器。这些加热器被设计成能够快速且均匀

cs61C | lecture4

cs61C | lecture4 C 语言内存布局 ### Stack 在最顶部,向下增长。包含局部变量和 function frame information。 > Each stack frame is a contiguous block of memory holding the local variables of a single procedure. > A stack fra

智能数据分析(1)Lecture 6-8b

Lecture 6: Generative Models 生成模型 vs 判别模型 判别模型(Discriminative Models) 判别模型的主要任务是直接学习输入 x x x 和类别 y y y 之间的关系。它们不关心数据的生成过程,而是直接估计类别的边界。 定义:判别模型直接学习 p ( y ∣ x ) p(y|x) p(y∣x),即在给定输入 x x x 的情况下,属

【计算机视觉】Lecture 22:相机运动

移动的相机 相机拍摄由时间t索引的图像(帧)序列 从一个时间到下一个时间,相机经历旋转(滚转、俯仰、偏航)和平移(tx、ty、tz) 运动(位移)场 运动场Motion Field和光流Optic Flow 运动场:三维相对速度矢量在二维图像平面上的投影 光流:在图像中观察到的亮度模式(brightness patterns)的二维位移 运动场是我们想知道的。 光流是我们可以

【计算机视觉】Lecture 20:八点法

提醒 本质/基础矩阵 本质矩阵和基础矩阵都是 3x3 的矩阵,用于“编码”两个视图的对极几何。 动机:给定一张图像中的一个点,乘以本质/基础矩阵将告诉我们在第二个视图中沿着哪个极线搜索。 本质/基础矩阵总结 Longuet-Higgins方程 极线: 极点: 本质矩阵 vs 基础矩阵: 本质矩阵E在成像坐标上作用(内参校准相机) 基础矩阵F在像素坐标上作用(未内参校准

【计算机视觉】Lecture 19:本质矩阵和基础矩阵

对极几何 左边 极点:相机1所看到的相机2的位置。 右边 极点:相机2所看到的相机1的位置 对极几何 对应点位于共轭极线上 对极几何 给定一幅图像中的一个点,我们如何确定在第二幅图像中要搜索的对应极线? 本质矩阵Essential Matrix 本质矩阵和基础矩阵都是 3x3 的矩阵,用于“编码”两个视图的对极几何。 动机:给定一张图像中的一个点,乘以本质/基础矩阵将告

【计算机视觉】Lecture 18:广义的立体视觉:对极几何

广义的立体视觉 主要思想:任何两张有重叠视图的图像,它们都可以被视为一对立体图像 我们只需要弄清楚这两个视图是如何关联的 视觉中一些最“漂亮”的数学问题是描述多个视图之间的几何关系。 回忆:对极约束(Epipolar Constraint) 重要的立体视觉概念: 给定左图像上的一个点,我们不必在整个右图像中搜索对应的点 “对极约束”将搜索空间缩小为一条一维的直线。 回顾:简单的立体

【计算机视觉】Lecture 17:拼接与稳定

回忆:平面投影 回忆:射影(逆)变换 回忆:平面投影 应用:稳定(Stabilization) 给定一系列视频帧,将它们变换到一个公共图像坐标系 这样“稳定”了视频,使其看起来好像相机不动一样。 稳定例子 链式稳定 如果参考图像没有与所有源图像重叠怎么办?只要有成对重叠,我们就可以连锁(合成)成对单应性变换。 不建议用于长序列,因为对齐误差会随着时间累积