标签:null har 情况 分析 最大值 手动 主函数 字符 针对
预处理:头文件展开和宏定义展开条件编译
gcc -E hello.c -o hello.i
编译:语法检查,生成汇编代码
gcc -S hello.i -o hello.s
汇编:声明目标代码,无法执行,因为缺少链接
gcc -c hello.s -o hello.o
链接:链接动态库,比如windows中的dll文件
gcc hello.o -o a.out
提前说明占用多大空间。
原码和普通数字的区别:负数最高位是1,其余7位保持等价。(共八位)
反码和原码的区别:负数最高位不变,其余为取反。
补码和反码的区别:补码=反码+1
补码的意义:符合位和其他位可以等同运算,如果最高位产生进位,则可以舍弃
补码求原码:(下面的步骤仅对于负数,正数不变)
在1的基础上加1
例如:
char a = 0x81;
printf("%d\n", a);
-127
除了十进制,其他进制计算机存储的都是补码,运算的时候要站在计算机的角度。
十进制可以站在用户的角度。
有符号和无符号的区别:
有符号最高位是符号位,无符号最高位是数的一部分。无符号没有负数
有符号的数的范围:
正数:
0000 0000 ~ 0000 0111
0 ~ 127
负数:
1000 0000 ~ 1111 1111
-0 ~ -127
需要注意的是-0可以当作-128使用
原因:-128的原码和补码是一样的
原码:1 1000 0000
反码:1 0111 1111
补码:1 1000 0000
数值越界:原因计算机存储数据都是以二进制补码的形式来进行存储的
char a=127+2
printf("%d",2); //输出位-127
sizeof(数组名)
面试经常提问
%p
它们的ASCII码不同,打印会有所不同,‘0‘的ASCII码是48,\0的ASCII码是0
遇到空格截断scanf("%s", str); str=hello world输入的是hello,空格后面的字符放在了缓冲区
scanf()的缺陷,不做越界检查,所以这个安全隐患,VS不让用这个函数,除非加上了宏定义
种子需要每次都改变,随机数才会发生改变,通常情况此下,用时间作为种子
gets:这个函数已经被抛弃了,它的功能是从键盘读取字符串,放在指定的数组。它不作越界检查,函数不安全。
fgets:从标准输入读取内容,如果输入内容大于sizeof(buf)-1,只取sizeof(buf)-1个字符,放在buf数组,需要注意的是它会把字符也读取进去。
puts:把buf内容输出到屏幕,自动在屏幕加换行,字符串本身没有换行符,是函数本身的。
fputs:和printf一样的效果,只是在文件操作时较为方便。
return:中断函数,中断main函数,程序停止
exit:结束整个程序(结束进程)
同一个文件如何防止多次包含:
#pragma once //包含多次只有一次生效
#ifndef _MyFunction
#define _MyFunction
#endif
分文件编程不一定是一个函数分一个函数
运行程序,加载到内存,运行这的程序叫做进程
//定义变量,分配内存
每个字节的内存都有标号,这个标号就是地址,也叫指针
地址需要存储,32位编译器用32位(4字节)存此地址。
64位编译器用64位存地址
指针也是一种数据类型
p是一个变量,p的类型是int *,p的地址是&p,p里面存的也是一个地址,它存的是某个变量的地址。
*p等价于*(p+0)等价于p[0]。如果写成p[1]会产生越界错误,这是因为操作了野指针,它等价于操作了*(p+1)的内存,因为p+1这个地址是未知的。
*在定义变量是,它表示类型;在使用变量时,它代表操作指针所指向的内存
段错误:指的是非法操作内存,数组越界或者指针错误
野指针(常考):这个指针变量保存了一个没有意义(非法)的地址
合法地址:只有定义的变量后,这个地址就是合法的。
野指针本身没错,操作了非法地址才会出错。
操作系统中将0-255作为系统占用不允许访问操作。程序中允许存在野指针,使用野指针可能会报错。
不建议将变量的值直接赋值给指针。
空指针是指内存地址编号为0的空间,操作空指针一定会报错。空指针可以用于条件判断
数字0和‘\0‘和NULL都是一个意思,只不过NULL常用于指针,前两个多用于字符串。
多级指针:在需要保存变量地址的类型基础上加上一个*
万能指针:void不可以定义普通类型的变量,不能确定类型,就不能知道分配多大的空间。
可以定义void* 的变量,void*指针也叫万能指针
void*可以指向任何类型的变量(类型匹配),使用指针所指向的内存时,最好转换为它本身的指针类型(因为要指定明确的操作的内存空间大小)【*(int* p))】里面的int*就是指定了空间大小
上面说的指定的内存空间大小就是指的是指针步长。p+1指的是加的是一个步长,而不是数学上的1。步长由指针指向的数据类型决定。
不做类型转换,无法确定步长
面试经常问的:
const修饰,代表指针所指向的内存是只读:const int/int const p;
const修饰指针变量,代表指针变量的值为只读:int* const p = &a;
const同时修饰指针和变量,代表只读:const int * const p = &a;
数组名是常量,不允许修改,int* const p
它是数组,每个元素都是指针
它是指针,指向数组的指针
能够通过函数间接的操作内存。
如果想通过函数来改变实参,必须地址传递。
形参中的数组,不是数组,而是普通指针变量
形参数组:int a[10000],int a[],int a对编译器而言,没有任何区别,编译器都当作int 处理,64位系统中sizeof(a)的结果是8,而非数组的长度,sizeof(a[0])的结果就是4,因为第0个元素是int类型。
非形参的数组就是真的数组了
err,二维数组不是二级指针
下面的程序面试会经常问到:
int* fun(){
int a;
return &a; //err
}
int main(){
int* p=NULL;
p=fun();
*p=100;//操作了野指针
}
上面的代码在不同的平台运行有的会成功有的不会成功,但是是错误的(VS和Qt通过,Linux未通过)。因为当fun函数执行完毕后,变量a被释放。再次操作a的内存时就是操作了野指针。
linux64位gcc,不允许返回局部变量地址,因为返回后会无意义,会引起操作野指针的错误。
定义的全局变量可以解决上面的错误。
全局变量在任何地方都能使用,全局变量只有在整个程序结束后才释放。全局变量在data区存放
%s:打印一个字符串
打印的时候%s会做一个额外的处理,所以会一次行打印出字符串,传入的是一个首元素的地址。
%s占位符,逗号后面对应的是一个地址,计算机会打印这个地址后面的所有字符,直到遇到\0停止打印。
%d,占位符,传入的的是一个变量,打印的也就是这个变量的值。
strcpy是拷贝给p所指向的内存空间拷贝内容,而不是给p拷贝内容。如果没有定义p指向的内存空间,就会出现操作野指针的错误(段错误)。
以字符串双引号形式初始化数组,自动隐藏结束符‘\0‘。
如果以单个字符类似数组的方式的赋值,打印%s会出现乱码。
每个字符串都是一个地址,这个地址是指字符串的首元素的地址
字符串常量放在data区,文字常量区。只读,不能修改。(面试经常考)
int* p = "hello"
strcpy(p,"abc")//error 段错误
main函数的参数:argv[]等价于*argv,也可以写成char* *argv
main函数的参数是由用户运行的时候传递的。
如果要指向上一个指针的地址,那就比上一个指针多一个*就可以了
strstr()函数的使用
char* p1 = "abc" //存放在文字常量区的指针变量,不不能修改,可以拷贝到栈区就可以修改了
char p2[] = "abc" //存放在栈区,可以改变数组元素
char p3[100]; p3="abc"//err数组名是常量
作用域和生命周期。
需要注意的就是当内存释放了就不要使用了,这是非法操作内存。
static局部变量,在编译阶段就已经分配了空间,函数没有调用前,就已经存在了。只有程序结束,static变量才自动释放。如果不做初始化,默认为0。这个语句只会初始化一次,但是可以赋值多次。
普通局部变量和static局部变量的区别
char** p。p是char**类型,指向char*类型
C语言全局变量多次定义不会出现编译不会报错
int a;
int a;
int a;
上面的代码中有一次是定义,两次是声明。但是不能确定那个是定义。
如果定义一个全局变量,没有赋值(初始化),无法确定是定义还是声明。
如果定义一个全局变量,同时初始化,那么这个就是定义,其他语句是声明。
只有声明,没有定义,无法给变量赋值
因此,定义全局变量的同时,建议初始化,声明一个全局变量,加extern关键字。
在main函数里面或者外面声明都可以
使用函数前,声明函数
声明函数,extern可有可无
如果事先为声明,gcc编译会通过,但是会有警告,c++编译器不会通过。因此建议先声明函数,后使用。
声明不需要加头文件
一般来讲头文件声明,.c文件声明不定义(对于同一个文件而言。)在main函数中直接包含头文件
全局变量在任何位置都只能定义一次,不管两个文件有没有关系。
如果在头文件中做定义,就可能会在预处理头文件展开时,会出现重复定义的问题。
面试会问:为了数据安全,把一个数据定义为static全局变量
static:内部链接
普通全局变量:外部链接
普通函数和static函数的区别和变量是一样的
auto变量:作用于一对{}内,声明周期为当前函数
写代码一般不会用到,但是面试会可能会问到
size a.out #查看分区
在程序没有执行前,有几个内存分区已经确定,虽然分区确定,但是没有加载内存,程序只有运行时才加载内存。
当运行程序,加载内存,首先根据前面确定的内存分区先加载,然后额外加载两个区:
栈越界就是栈区空间用光了
语法上没有问题,栈区分配很大的内存,越界了,导致段错误。段错误都是内存错误引起的。
这种错误通常是出现在函数递归的时候。
和strcpy, strncpy, strcmp类似,但是如果是字符串,遇到\0结束是无法解决的。但是内存操作函数可以解决这个问题。
memset(&a, 97, sizeof(a));
其中97是以字符的ascii码来对a进行赋值的。主要用来清0数组。
这个操作的第二个参数是针对字符来用的,不是整数。
其他函数:memcpy
, memmove
, memcmp
如果出现内存重叠,最好使用memmove
代替memcpy
定义一个变量,指针指向该变量地址即可
使用malloc函数在堆区申请一个空间
参数指定堆区分配多大的空间malloc( sizeof(int) )
返回值,成功就是堆区空间的首元素地址,失败返回NULL
注意返回的地址是void*
类型的,需要类型转换为int*
,如果不转换,gcc编译可以通过,但是c++就会失败。
释放空间用free(p)
,不是释放p变量,而是释放p所指向的内存
同一块堆区空间只能释放一次
所谓的释放不是内存消失,而是指这块内存用户不能再使用,系统回收了,如果用户再次使用,就是使用野指针。
动态分配了空间,不释放
int* p = NULL
.....
if(NULL != p)
{
free(p);
p = NULL;
}
非法操作内存:操作野指针所指向内存,堆区越界
下面是错误代码,没有分配空间,但是却gcc编译成功。vs运行程序会卡死
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *agrv[]){
char *p = NULL;
p = (char *)malloc(0); //没有分配空间
if(p==NULL){
printf("分配失败\n");
return 0;
}
strcpy(p, "helloworld");
printf("%s\n", p);
free(p);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *agrv[]){
char *p = NULL;
p = (char *)malloc(0); //没有分配空间
if(p==NULL){
printf("分配失败\n");
return 0;
}
strcpy(p, "helloworld");
printf("%s\n", p);
free(p);
return 0;
}
因为数组在内存中的存储是连续的
Java和C++封装了内存释放函数,用户只需要使用,但是C语言没有这种封装,所以会涉及到的内存问题较多。
p = (int * )malloc( 10 * sizeof(int) )
相当于在堆区开辟了类似栈区数组 int arr[10]; int *p = arr;
值传递:
void fun(int *tmp){ //tmp存储在栈区空间
tmp = (int *)malloc(sizeof(int) ); //操作堆区空间的内存
*tmp = 100;
}
int main(int argc, char *argv[]){
int *p = NULL;
func(p); //值传递,形参修改不会影响实参
printf("*p = %d\n", *p); //err,操作空指针
}
一个结构体变量就是C++中的一个对象
关键字 struct
struct Student{
;
}; //有分号,另一个就是do while了,两个特殊例子
struct Student合起来才是结构体类型名字
结构体内部定义的变量不能直接赋值
结构体只是一个类型,没有定义变量前,是没有分配空间的,没有分配空间,就不能赋值。
struct Student stu;//定义变量
结构体变量初始化,和数组一样,要使用大括号,而且只有在定义时才能初始化。
struct Student stu2 = {10, "mike", 40};
如果是普通变量,使用点运算符赋值。
如果是指针变量,使用->
运算符;如果是指针,指针有合法指向,才能操作结构体成员。
任何结构体变量都可以用点或者箭头操作成员
标签:null har 情况 分析 最大值 手动 主函数 字符 针对
原文地址:https://www.cnblogs.com/thethomason/p/9831713.html