嵌入式linux C整理——C语言基础

  最近秋招刚拿了几个offer,想记录一下秋招时期个人整理的一些笔/面试笔记,作为备份供自己回顾,同时也分享给学习嵌入式相关行业的其他需要的同行者~

一.数组

1.定义

  int a[5],表示数组有5个int类型元素,首元素是a[0]

1.1输入输出

int a[100]; scanf("%d", &n);
for(i=0; i<n; i++)	
	scanf(%d”, &a[i]);
for(i=0; i<n; i++)
	printf(%d”, a[i]);

2.初始化

2.1一维数组

Int a[]={0}; √赋值参数个数决定数组的个数
int a[10]={}; √值随机 int a[10]= {0} √部分赋值,后面的全部为0
int a[n]={0}; //报错,使用变量定义数组长度时,不可在定义同时进行初始化赋值

2.2二维数组

int a[][3]={{1,0,1},{},{1,1}}; √
int a[][3]={1,2,3,4,5,6,7};
int a[2][4]={{1,2,3},{4,5},{6}};越界!
ps:二维数组初始化可省略行 行可以根据放的列知道,但列不写不知道一行放几个。

3.a与&a(数组名与数组名取地址)

地址数值:
(1)a    =a[0]   = &a   = 4fd5d030
(2)a+1  = &a[0]+1    = 4fd5d034 +4B
(3)&a+1         = 4fd5d044 +5*4B
(4)(&a+1)         = 4fd5d044
在这里插入图片描述

  a+1是偏移单个元素问题:数组首元素地址 的基础上,类型为a的指针的移动,是以sizeof(a[0])为移动单位 a+i = a +i*sizeof(a[0]);

  &a相当于二重指针,数组首地址的地址(二维数组也一样,偏移整个数组);
  &a+1:在整个数组地址 的基础上,偏移整个数组sizeof(a)单位。&a+i = a + i*sizeof(a)

4.二维数组a

a表示:以a[0][0]为首元素的二维数组	*a(=a[0])表示:a[0][0]为首元素的一维数组.		

4.1表示取值!

int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23} //三行四列
int *p = a;  	//p=a[0][0]
* (*(p+i)+j)

C中: *(p+i) = p[i]

a[i][j] == *(a[i]+[j]) == *((*b+i)+j) == (*(b+i))[j]   
a[2][1] = 19  三行二列

4.2表示地址!

  &a[1]:以a[1][0]为首元素的二维数组. (&a[1])[i][j] 即a[i+1][j]   &a[i][j] == p[i]+j == *(p+i)+j

运算符'+':指针操作,执行的都是地址的加法,而且有的加1是指向同一行中的下一列上的元素(如*a、a[0]和a[1]),有的加1是指向同一列中的下一行上的元素(如a和&a[1]).
对于运算符'*':*a、a[0]和a[1]执行的是取值,如**a == *a[0] == a[0][0]  *a[1]== a[1][0]

5.变长数组——结构体内不占长度

5.1主要用途

  为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题。

5.2用法

  在一个结构体的最后 ,申明一个长度为空的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个符号,代表一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量 (注意:数组名不是指针! )

typedef struct _SoftArray{
    int len;
    int array[];		//0
}SoftArray;

5.3限制

  1. 变长数组不能用static或者extern修饰;
  2. 变长数组必须在程序块的范围内定义;

6.数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址

二.结构体

1.结构体操作

结构体变量:

a. 两个结构体变量的类型相同,可以赋值(复制成员的值)
b. 结构体变量不能相±*/ ,关系运算><==.(除非进行运算符重载C++)

结构体的成员访问(数组/指针都用.)

结构体指针访问成员时: p->num

2.sizeof

2.0概念和使用

  sizeof()是一个判断数据类型或者表达式长度的运算符,编译阶段进行处理。
(1)sizeof(函数),但不执行函数体。结果是函数返回类型的大小,和返回值无关。
  如:int fun() { float a; return a } sizeof(fun) = 4 (与float a无关)
(2)sizeof(表达式),不对表达式求值返回表达式的计算结果的类型大小(sizeof (a=a+b), a的值不变)
能求void *但不能求void类型的长度

2.1与strlen区别:(代码见字符串) -----------strlen不包括’\0’

----------------char a[] = “xxx”时,sizeof(a)=strlen(a)+1------------------
  a. sizeof操作符的结果类型是size_t,它在头文件中typedef unsigned int size_t 。

  1. sizeof操作符的结果类型是size_t,它在头文件中typedef unsigned int size_t 。该类型保证能容纳实现所建立的最大对象的字节大小。
  2. sizeof是运算符,strlen是函数。------头文件string.h
    sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。
    strlen(char*)函数求的是字符串的实际长度,直到遇到第一个’\0’,然后就返回计数值,且不包括’\0’
  3. strlen只能用char*做参数,且必须是以’’\0’'结尾的。
  4. 数组做sizeof的参数不退化,传递给strlen就退化为指针了
  5. sizeof(x)可以用来定义数组维数,计算类型/变量长度char str[20]=“0123456789” sizeof(str);// 20
strlen

  \ddd八进制

2.2Sizeof 数据类型的大小

在这里插入图片描述

case

注:指针4/8(包括函数指针void(*Jump)(void); )
保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍;

1:在32位cpu上选择缺省对齐?的情况下,有如下结构体定义:
struct A{	unsigned a : 19;	unsigned b : 11;
    		unsigned c : 4;		unsigned d : 29;	char index;	};sizeofstruct A)的值为(16unsigned a : 19表示成员占19bit位域
19+11<32    4+29>32  ------ 4*32/8=16B

例2sizeof(C)=2464位系统中
class C 
{ public:
     char a;				//1,类自动对齐,补上7字节====8
     static char b;			//类中的静态变量为0,类外不为0
     void *p;				//8,64位
     static int *c;			//0
     virtual void func1();	//8.两个虚函数只要一个虚函数指针
     virtual void func2();
 };3sizeof(xc)=2016位操作系统
Struct stu
{	union{						//共用体最长成员是char b[5],大小取5字节,
char b[5];				//但最宽数据成员是int,故2字节对齐,5+1=6
int bh[2];
}class;
char x[8];					//结构体最宽数据成员为float,故以4字节对齐
float c;					//6+2=-8补齐,	8+8+4=20
}xc;

3.struct与union区别

   结构体:各成员各自拥有自己的内存,各自使用互不干涉,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。——需要修改成员时使用
   共用体:各成员共用一块内存空间,同时只有一个成员可以使用这块内存,对某一成员的赋值,会修改其他成员的值。——比结构体更省空间

sizeof union

计算准则 :1.至少能容纳最大的成员变量 2.要满足是所有成员变量(最大)类型大小的整数倍。
union的字节数 == 占用字节最多的成员的字节 * n>= max成员占用内存 。

union u2 24
{
char a;
int b;
short c;
double d;
int e[5];
}U3; 至少容纳最大e[5]=20字节, 变量类型最大double(8)的整数倍:24

位域:
typedef struct_data{		11
	char m:3;			3+5 = 1B
	char n:5;
	short s;			2B
	union{			4B
		int a;		4
		char b;
	};
   int h;				4B
}_attribute_((packed)) data_t;		取消对齐

4.内存对齐

  按照成员的声明顺序,一次安排内存,其偏移量为成员大小的整数倍,0看作任何成员的整数倍,最后结构体的大小为最大成员的整数倍。

4.1为什么要内存对齐?(硬件限制要求对齐,但牺牲内存空间换取速度性能)

 硬件限制:硬件本身有物理上的限制,导致处理器在对齐排布和访问时效率更高(如32位机,每次取32位/4字节,同样取1个int,字节对齐时取1次,不对齐要2次)
 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。因为为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问只需要一次。

4.2内存对齐有啥优点

 程序的执行效率提高:减少了访问内存的次数
 原子性得到保障:CPU能以原子方式操作对齐的内存,没有其他指令可以中断该操作
 访问范围提高:对于任意给定的地址空间,相差2个LBS,四个位

4.3如何改变内存对齐方式?

 #pragma pack(n),预编译伪指令,C编译器将按照结构体内各元素n字节对齐(gcc最高4)
attribute((aligned(n))),指定结构体变量整体n字节对齐----但最大成员大于n时仍按最大的对齐
typedef struct st1 { int a; char b; short c; short d; }attribute((aligned(8))) My1;
attribute((packed)),取消编译器的优化对齐

 #pragma pack(4)
    struct node{
      int e;		//4
      char f;		//2	结构体内部自动对齐,short 2
      short int a;	//2
      char b;		//4(若置于short int前,则总体=8;或者改为2字节对齐=10)
    }n;
printf("%d\n",sizeof(n));	12

typedef struct s
{
    void *pfunc;        //4
    char* str;          //4
    unsigned int val;   //8
    unsigned long long b_val;//8
}ST __attribute__((aligned(4)));	24

三.字符串

1.指针++输出

main() { char *p = “abcdefgh” ,*r;
long *q; q=(long *)p;
q++; r=(char *)q;	 printf("%s",r); }	long4字节,==== efgh

2.与sizeof/sizeof相关

!!!void Foo (char str[100]){ sizeof(str) = ? } 4 数组作为函数形参进行传递时会退化为指针。(传递数组首地址—>指针)

char ss[]="0123456789";
sizeof(ss)结果11===》ss是数组,计算\0,因此是10+1
sizeof(*ss)结果1===*ss是第一个字符

char ss[100]="0123456789";
sizeof(ss)结果是100===》ss表示在内存中的大小100×1
strlen(ss)结果是10===》strlen是个函数内部实现是用一个循环计算到\0为止之前

int ss[100]="0123456789";
sizeof(ss)结果400===》ss表示在内存中的大小100×4
strlen(ss)错误===》strlen的参数只能是char*且必须是以'\0'结尾

char q[]="abc";
char p[]="a\n";
sizeof(q),sizeof(p),strlen(q),strlen(p);		//4,3,3,2

sizeof 一个引用得到的结果是 sizeof 一个被引用的对象的大小
struct test{};	test&r=*newtest;

3.字符串存的位置

// 字符串存在栈上-------栈变量是从上到下存放的,先定义的a地址&a>&p
	char a[7];	
	char *p = a;

	// 字符串存在数据段(全局变量)  类似于char a[]=“hello”
global char b[5];
	char *p = b;	

	// 字符串存在堆空间
	char *p = (char *)malloc(5);	//字符串特有,使用更灵活

// 字符串常量在代码段
const char a[]=“hello”		//字符串常量在代码段/常量区
两个指针p1/p2指向一个字符串常量,则两者的值相同!!
静态变量/全局数据非0初始化放在 .data	
0初始化放在 .bss

4.字符串函数相关

拷贝函数中memcpy执行效率高(strcpy)

  1. String和char*/char[]
    strlen是计算char */char[]的, 不能测string,要用length();
  2. 字符串定义
    Char* p, s[10], mark[]; p = “hello”; √ s = “hello”; ×(s+=1 ×) mark = “hello”; ×
    数组名是左值(常量指针)不能直接赋值,应该strcpy(s, “hello”),字符数组未初始化得char mark[]=“hello”

四.指针

★2.0野指针 产生原因、后果、如何避免(非NULL)

原因?——指针指向的位置不可知

 定义指针时未初始化:会随机指向一个值(stctic int *p 默认=0)
 指针被释放时没有置空:指针指向的内存空间在用free()和delete()释放后,未置空或者赋值
 指针操作超越了变量作用域:返回指向栈内存的指针或引用,栈内存在函数结束的时候会被释放。

https://blog.csdn.net/ipmux/article/details/17549157

//指针操作超越了变量作用域
char *GetString(void) {
  char array[6] = "hello"; 	//修改处,定义数组=hello,数组内容为hello,返回栈内容
  return array; } 
void main(){
  char *pstr = GetString();	//返回指向栈内存的指针
printf("%s\n", pstr); }

修改为:
1. char *array = “hello”; // "hello"串位于常量数据区,p指向的是hello在内存中(数据区)的地址,在子函数结束后依然有效
2. 添加char *p = malloc(6); strcpy(p, array); return p; //堆内存中仍有效

后果:3种情况

  1. 指向不可访问的地址,触发段错误。——直接报错无危害
  2. 指向正在被使用的可用内存空间(某个变量值),导致程序崩溃/数据被篡改!——危害最大
  3. 指向一个可用的、而且没什么特别意义的地址,此时会掩盖程序错误,误以为程序没问题——无大危害

如何避免?

  1. 定义指针同时初始化为NULL,之后再对其进行操作。
  2. 使用之前,将其赋值绑定给一个可用地址空间
  3. 解引用之前,先去判断它是不是NULL
  4. 使用后/释放指针后( free)就将指针置为NULL
int *p = NULL;	//1.
………
p = &a;			//2. 
if (NULL != p)	//3.
{	*p = 4;	}
p = NULL;		//4.

2.1指针定义

Int *p=1000, a=1000	(一般是int *p=&a;==== int *p; p=&a; )
*p==a 	×		此时p=0x1000(p指向地址),*p=随机数
p!=a  	×		==	值是相同的
&p!=&a	√
p==&a 	×	  	&a未知

2.2与其他结合

a、数组指针:int (*p)[3];	------p为指向含3int型元素的一维数组的指针变量-------------- int(*)[3];	 
b、指针数组:int* p[3];	---单目从右到左结合,数组由3个指向int型的指针元素组成(先结合为数组) 
c、函数指针:int (*fun)();-----一个指向函数fun的指针。 --------------------------int (*)()
d、指针函数:int* fun();------一个返回指向整型值指针的函数
例1:

ptr是返回值为int指针的含有3个(指针)元素(的数组)的指针函数 -------- int *(*ptr[3])()

例2:

声明一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int*,正确的是

int (*(*p)[10])(int *)-------元素是函数指针int (*pf)	(int *)	数组指针(*p)[10] 作为一个整体替代 pf
int (*( (*p)[10] ) )(int *)		表达式由内向外分析
分析: 判断一个复杂式子看最高优先级的,*p是一个指针,然后(*p)外面是[],所以是数组指针,(*p)[10])描述完毕,然后再看外面int(*)(int *)很明显,这是一个函数指针,所以这个数组中每个元素是函数指针。
例3:-------指针数组与一维数组

Int p[4],a[12]={1…12}; // 定义了指针数组p,数组里有4个元素都是int类型
p[3]=&a[9]; =10 // 第三个元素p[3]指向a[9]的地址 p[3]=p+3
则p[3][2]= p[3]+2=12 // p[3]是Int *类型的,偏移2各单位后指向a[11]

函数指针调用

int Max(int x, int y); 
int (*p) (int x, int y);	/*定义函数指针*/
p = Max;      		/*将Func函数的首地址赋给指针变量p*/
c = (*p)(a, b);  		//通过函数指针调用Max函数

2.3. const结合

char a[] = "abcdefg";
const char *p = a;      //常量指针,p指向的内容不可更改
char b[]="123";	memcpy(a,b,sizeof(b));	//可以通过a改指向的内容
p = "xyz";   	//p可以指向别的地址
*(p+3)= 'k';	//err

//常量指针。普通指针,指向一个常量,不能指向变量,不能通过指针来修改它指向的内容
//指针本身是变量,它自身的值可以改变,从而指向另一个常量

2.4.多重指针

Case1:修改代码,黄色为修改/添加部分

void GetMemory(char **p, int num){-----------------------二级指针
	if(num<=0)
        printf("申请的内存空间要大于零!\n");
    *p = (char*)malloc(num);
    if(*p==NULL)
        printf("申请内存失败!\n");	//申请失败判断
	return -1;
}
void GetMemory2(char *p, int num){-----------------------一级指针
	p = (char *)malloc(num);
}
void test(){
    char *str = NULL;
    GetMemorty(&str, 100);
   	strcpy(str, "hello world");
   	printf("%s\n", str);		 		//printf( str );  少了
   	free(str);						//不free掉,内存泄漏
  	str = NULL;					//野指针
}	

1.一级指针情况下:调用void GetMemory2输出NULL,函数的操作并没有影响str。
很明显,指针就是传个地址给函数参数,那么p也是指向空。而操纵的是p而对str没有影响。
在这里插入图片描述
2.二级指针下:调用void GetMemory 输出hello。p就是str的地址,操作的也就是str,即给p分配内存。===============》对于函数传字符串或者通过malloc分配内存,都需要使用二级指针。
在这里插入图片描述

宏相关

1.寄存器操作

宏定义+volatile寄存器地址

#define rDATA           ((*(volatile unsigned int *)0x80000000)
#define rSTATUS         (*(volatile unsigned int *)(rDATA + 0x04))
int read_reg_data(unsigned int* dest)
{
    assert(dest != NULL);
    if(rSTATUS & (1<<31)){
        *dest = rDATA;
        rSTATUS &= ~(1<<31);
        return 0;
    }
    return  -1;
}
(volatile unsigned int *)0x80000000把值强转为unsigned int类型指针,且指向0x80000000地址空间。
	前面加上“*”指针解引用,致使对该变量的操作就是对寄存器内容的操作。(类比 #define A *p)

typedef结构体+attribute(at( )) 绝对定位

typedef struct
{
    volatile const unsigned int r_data;
    volatile unsigned int r_status;
}s_regs __attribute__(at(0x80000000));	//绝对定位
int read_reg_data(s_regs* reg, unsigned int* dest)
{
    assert(reg != NULL && dest != NULL);
    if(reg->r_status & (1<<31)){
        *dest = reg->r_data;
        reg->r_status &= ~(1<<31);
       

Linux内核中2宏定义

先用offsetof计算type结构体成员member在结构体中的偏移量,然后ptr的地址减去这个偏移量,就得出type结构变量的首地址。

offsetof    计算结构体成员偏移量(Linux内核)

#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
 (TYPE*)0:将0强转为TYPE类型的指针,且指向了0地址空间,即值为0的TYPE指针;
 ((TYPE*)0)->MEMEBER:指向结构体中的成员,表示MEMBER元素;
 &((TYPE*)0->MEMBER):获取成员在结构体的位置,表示MENBER的地址;
   注:基地址为0,所以获取的地址即为实际的偏移地址。
 再把结果强制转换为size_t型的就OK了,size_t其实也就是unsigned int。

container_of  得到结构体的首地址

 ({ })这种语法形式是GNU C编译器的语法扩展,与逗号表达式类似,表达式结果为最后一个语句的值
 typeof是GNU C编译器的特有关键字,只在编译期有效,用于得到变量的类型
 ptr是指向正被使用的某类型变量指针

#define container_of(ptr,type,member)({\
    const typeof(((type*)0)->member) *__mptr=(ptr);\
(type*)((char*)__mptr – offsetof(type,member));})

   注:功能是计算返回包含ptr指向的变量所在的type类型结构变量的指针(结构体首地址)。
typeof(((type*)0)->member)是引用type结构体的member成员的数据类型;
定义一个与member相同类型的指针变量__mptr,且将ptr值赋给它;
用宏offsetof(type,member),获取member成员在type结构中的偏移量;
最后将__mptr值减去这个偏移量,就得到这个结构变量的地址了(亦指针)

其他常用

标准宏MIN(括号!)与一年的秒数(UL)

#define MIN(A,B)		( (A) <= (B) ? (A) : (B) )
#define Second_Per_Year	(365*24*60*60)UL			//16位会溢出UL

取整

数值x按照a的整数倍向下取整:#define ALGN_DOWN(x,a) ((x)-(x)%(a))
向上取整:#define ALGN_DOWN(x,a) ((x)+(a)-(x)%(a))
向下取整,a是2的n次幂:#define ALGN_2N_DOWN(x,a) (((x)&~(a-1))
向上取整:#define ALGN_2N_DOWN(x,a) (((x)+(a-1))&~(a-1))

小端大端转化(htons:short类型主机转网络字节序0x1234-3412)—移位优先级比位运算高

16位:#define BigLittleSwap16(A) (((uint16)(A) & 0xff00) >> 8 | ((uint16)(A) & 0x00ff) << 8)
32:#define BigLittleSwap32(A) (((A) & 0Xff)<<24 | ((A) & 0Xff00)<<8) | ((A) & 0Xff0000)>> 8) | …)

本书所附光盘使用说明 本光盘中包括了书中所有示例的源代码和书中所有的插图,具体说明如下。 程序代码文件夹中包含了本书的所有源代码。 程序代码\chapter02 文件夹中包含了第2章的示例源程序。其中hello.c和hello.h是2.3.2的源代码,gdb.c是2.4.1的源代码,test.c是动手练练的源代码。 程序代码\chapter06 文件夹中包含了第6章的示例源程序。其中pointer1.c是6.2.2的第一个源代码,pointer2.c是6.2.2的第二个源代码,pointer3.c是6.2.2的第三个源代码,pointer4.c是6.2.3的第一个源代码,pointer5.c是6.2.3的第二个源代码,pointer6.c是6.2.3的第三个源代码,pointer7.c是6.2.3的第四个源代码。 程序代码\chapter08 文件夹中包含了第8章的示例源程序。其中binary_tree.c是二叉树的源代码,list.c是线性链表的源代码。 程序代码\chapter09 文件夹中包含了第9章的示例源程序。其中lock.c是文件锁的源代码,seri.c和seri.h是串口设置的源代码,read_seri.c是读串口的源代码,write_seri.c是写串口的源代码。 程序代码\chapter10 文件夹中包含了第10章的示例源程序。其中alarm_read.c是设置信号函数的源代码,dameon.c是守护进程的源代码,zombie.c是僵尸进程的源代码。 程序代码\chapter11 文件夹中包含了第11章的示例源程序。其中socklib.c是网络相关通用函数的源代码,tracert.c是traceroute的源代码,webserv.c是web服务器的源代码。 程序代码\chapter12 文件夹中包含了第12章的示例源程序。其中skull.c是skull驱动程序的源代码,s3c2410fb.c和s3c2410fb.h是LCD驱动的源代码。 程序代码\chapter13 文件夹中包含了第13章的示例源程序。其中args_cmd.h是解析命令相关的头文件,ctrl.c和ctrl.h是控制命令的源代码,proc_cmd.c是具体操作的源代码,thread.c和thread.h是线程相关的源代码,types.h是类型相关的源代码,net_send.h是网络相关的源代码。 书中插图包含了本书所有的插图。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值