碎碎念
skip,上课笔记
1. 作业细节:
1) 例子
//L1-main.c
#include <stdio.h>
extern void do_reverse(void);
int main()
{
do_reverse(); // real work here
return 0;
}
// L1-eg.c
#include <stdio.h>
#include <stdlib.h>
void do_reverse(void) {
int l, i, c;
char *p;
p = (char *) malloc(256);
for (i=0; (c = getchar()) != '\n'; i++)
p[i] = c;
l = i;
for (; i > 0; i--)
putchar(p[i-1]);
if (l > 0) putchar('\n');
}
2) 要求
- 字符串输入的长度未知(注意到 L1-eg.c 是一个简单的例子,因为它假设了字符串的最大长度为16)
- 动态内存分配 必须 被用来储存input,如heap(堆)。
- 因为输入长度未知,所以global / local variable不可使用;
- 只可以使用 malloc( ) 来得到新的heap memory;
- 只可以使用 free( ) 来返回memory到heap中;
- 其他malloc库里的例程不可使用。 - 只能在一个c file中,并会在 L1-main.c调用。(free( ) 可以不用)
- heap中内存的使用应该和输入的长度一致。如,输入长度为 n n n, 那么heap usage space 应该是 O ( n ) O(n) O(n)。所以如果输入长度短,heap usage也小,即使用的heap memory也小。
- 唯一允许的文件I/O是在standard input(
s
t
d
i
n
stdin
stdin),standard output(
s
t
d
o
u
t
stdout
stdout),standard error(
s
t
d
e
r
r
stderr
stderr)流上。
- 不可以使用 f s e e k ( ) , u n g e t c ( ) , r e w i n d ( ) fseek(), ungetc(), rewind() fseek(),ungetc(),rewind()等其他Unix file system。 - 不可以改 L1-main.c
- 输入测试只考虑ASCII,并且第一行之后的字符将被忽略。
3) 评分标准
- Correctness:它将通过测试从短到长的各种输入。必须给出正确的行反转而不崩溃,即没有“segmentation fault”(Linux中的一部分内存错误)或其他运行时错误。
- Time/Space Efficiency:用时短,使用内存小:
- CPU user time:使用 t i m e time time 或者 / b i n / t i m e /bin/time /bin/time 命令;
- 对于特定输入的最大heap usage。
4) 其他
- 评分使用 -02 来运行代码(optimization level 2);
- 评分使用多种C编译器来测试代码(如clang complier)
- 通过评测 peak heap usage 来给heap usage 打分
- 在Linux默认情况下,输入来自stdin表示的I/O流。如果在终端中运行程序,则stdin使用该终端;输入也可以来自文件,shell(例如bash)为I/O提供输入和输出重定向,例如:
$ ./lab1 < test.txt
- 在上面的例子中,input redirection使输入可以从从文件“test.txt”中读取。
- 下面的例子表示了 input 和 output redirection:
$ ./lab1 < test.txt > output.txt
- 调试和测试:
- ASAN ( AddressSanitizer ) 是是编译器的一个选项,它可以帮助检测内存错误(gcc官方文件; ASAN文件) 。
- 当ASAN发现错误时,它将给出堆栈跟踪和行号(通过调试 -g 进行编译)。注意,默认情况下,ASAN还会检查内存泄漏,可使用ASAN_OPTIONS环境变量来关闭它$ gcc -fsanitize=address
2. 代码分析
2.1 关键字 extern
-
因为局部变量只可以在函数内有定义;全局变量可以在 本文件 中的定义处到文件结束处使用。
-
如果想在定义点之前引用某全局变量,应在引用前使用关键字 extern 对该变量进行“ 外部变量声明 ”,表明改变量是一个已经定义的外部变量。从声明处起,就可以合法使用该外部变量。
-
全局变量在main函数之后声明,若要使用此变量,需在调用它们之前进行extern声明:
#include <stdio.h> int max(int x,int y); int main(void) { int result; /*外部变量声明*/ extern int g_X; extern int g_Y; result = max(g_X,g_Y); printf("the max value is %d\n",result); return 0; } /*定义两个全局变量*/ int g_X = 10; int g_Y = 20; int max(int x, int y) { return (x>y ? x : y); }
-
如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,也需要在文件开头使用extern进行声明:
/****max.c****/ #include <stdio.h> /*外部变量声明*/ extern int g_X ; extern int g_Y ; int max() { return (g_X > g_Y ? g_X : g_Y); }
/***main.c****/ #include <stdio.h> /*定义两个全局变量*/ int g_X=10; int g_Y=20; int max(); int main(void) { int result; result = max(); printf("the max value is %d\n",result); return 0; }
2.2 指针
- 指针是一种保存 变量地址 的变量。
- 声明指针
int p
– 这是一个普通的整型变量int *p
– 首先从 p 处开始,先与*结合,所以说明 p 是一个指针, 然后再与 int 结合, 说明指针所指向的内容的类型为 int 型。所以 p 是一个返回整型数据的指针。int p[3]
– 首先从 p 处开始,先与[] 结合,说明 p 是一个数组, 然后与 int 结合, 说明数组里的元素是整型的, 所以 p 是一个由整型数据组成的数组。int *p[3]
– 首先从 p 处开始, 先与 [] 结合, 因为其优先级比 * 高,所以 p 是一个数组, 然后再与 * 结合, 说明数组里的元素是指针类型, 然后再与 int 结合, 说明指针所指向的内容的类型是整型的, 所以 p 是一个由 返回整型数据 的 指针 所组成的 数组。int (*p)[3]
– 首先从 p 处开始, 先与 * 结合,说明 p 是一个指针然后再与 [] 结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组, 然后再与int 结合, 说明数组里的元素是整型的。所以 p 是一个指向由 整型数据 组成的 数组 的 指针 。int **p
– 首先从 p 开始, 先与 * 结合, 说明 p 是一个指针, 然后再与 * 结合, 说明指针所指向的元素是指针, 然后再与 int 结合, 说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中, 所以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针。int p(int)
– 从 p 处起,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里分析, 说明该函数有一个整型变量的参数, 然后再与外面的 int 结合, 说明 函数 的返回值是一个整型数据。int (*p)(int)
– 从 p 处开始, 先与指针结合, 说明 p 是一个指针, 然后与()结合, 说明指针指向的是一个函数, 然后再与()里的 int 结合, 说明函数有一个int 型的参数, 再与最外层的 int 结合, 说明函数的返回类型是整型, 所以 p 是一个指向有一个 整型参数 且 返回类型为整型 的 函数 的 指针。int *(*p(int))[3]
– 可以先跳过, 不看这个类型, 过于复杂从 p 开始,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里面, 与 int 结合, 说明函数有一个整型变量参数, 然后再与外面的 * 结合, 说明函数返回的是一个指针, 然后到最外面一层, 先与[]结合, 说明返回的指针指向的是一个数组, 然后再与 * 结合, 说明数组里的元素是指针, 然后再与 int 结合, 说明指针指向的内容是整型数据。所以 p 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。
2.3 动态内存分配
- 动态内存 vs 静态内存
1.1 动态内存 是指在堆heap上分配的内存;由程序员通过编程手动分配释放,空间大,储存自由。
1.2 静态内存 是指在栈上分配的内存;有系统分配和释放,空间有限,在运行结束后会被系统自动释放 - 传统静态数组的缺点
2.1
数组长度提前声明,且只能是常量。
2.2
数组长度声明后无法更改。
2.3
一个函数中定义的数组只能在该函数运行期间被使用,此函数运行结束,数组自动释放。 - malloc 函数
3.1
malloc是一个系统函数,是memory allocate 的缩写,调用它必须在开始包含头文件 <stdlib.h> 。
3.2
void *malloc(int size) : malloc只有一个整型形参,功能为在内存的动态储存空间——heap堆中分配一个长度为size的连续空间,函数返回值为一个指向所分配内存空间起始地址的指针,类型为void型
3.3
void型表示未确定类型的指针。C中,void * 类型可以通过类型转换强制转换为任何其它类型的 指针。
3.4
malloc函数的返回值为一个地址,为动态分配的内存的起始地址;如果函数执行失败(如内存空间不足),则返回空指针 NULL 。
3.5
例如:
这段代码表示:请求系统分配4字节的内存空间,并返回第一字节的地址,并将其赋值给指针变量p。int *p = (int *)malloc(4); //或者 int *p = malloc(4);