Linux C 常见错误

1 函数指针

函数原型
int func(int x,int y);
int *func(int x,int*y);
定义函数指针
int (*p)(int, int);
int* (*p)(int, int*)
指向函数
p = func;
调用函数
p(1,2);//(*p)(1,2);
p(1,&a);
函数指针数组
#include<stdio.h>
#include<string.h>
int func1(int x)
{
    printf("file:%s func:%s line:%d value:%d\n",__FILE__,__func__,__LINE__,x);
}
int func2(int x)
{
   printf("file:%s func:%s line:%d value:%d\n",__FILE__,__func__,__LINE__,x);
}
int func3(int x)
{
    printf("file:%s func:%s line:%d value:%d\n",__FILE__,__func__,__LINE__,x);
}
int main(int argc, char const *argv[])
{
    int i;
    int (*p[3])(int)={					//定义函数指针数组
        func1,
        func2,
        func3,
    };
    for(i=0;i<3;i++)					//调用函数指针
    {
        (p[i])(i);
    }
    printf("hello\n");
    getchar();
    return 0;
}

2 多线程值条件变量

​ 条件变量:用条件变量用来自动阻塞一个加锁的线程,直到某特殊情况发生为止。

2.1 条件标量初始化函数

#include < pthread.h>
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
功能:
	pthread_cond_init函数可以用来初始化一个条件变量。使用变量attr所指定的属性来初始化一个条件变量,如果参数attr为空,那么将使用缺省的属性来设置所指定的条件变量。
参数:
	cont  条件变量
	attr  条件变量属性
返回值:成功返回0,出错返回错误编号。

静态创建:
pthread_cond_t   cond = PTHREAD_COND_INITIALIZER    

2.2 条件变量摧毁函数

#include < pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能: pthread_cond_destroy函数可以用来摧毁所指定的条件变量,同时将会释放所给它分配的资源。调用该函数的线程不会被该条件变量阻塞。
参数:
	cond	条件变量
返回值:
	成功返回0,出错返回错误编号。

2.3 条件变量等待函数(重点)

#include < pthread.h>
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t mytex,const struct 									timespec *abstime);
功能:
	阻塞调用该函数的线程,pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mutex,然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
参数:
	cond 条件变量
	mutex 互斥锁
返回值:成功返回0,出错返回错误编号。

 **第一个参数*cond是指向一个条件变量的指针。第二个参数*mutex则是相关的互斥锁的指针。函数pthread_cond_timedwait函数类型与函数pthread_cond_wait,区别在于,如果达到或是超过所引用的参数*abstime,它将结束并返回错误ETIME.pthread_cond_timedwait函数的参数*abstime指向一个timespec结构。该结构如下:

typedef struct timespec{
    time_t tv_sec;
    long tv_nsex;
}timespec_t;

2.4 条件变量通知

#include < pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);//按入队顺序激活其中一个
int pthread_cond_broadcast(pthread_cond_t *cond);//全部激活
功能:
	参数*cond是类型为pthread_cond_t 的一个条件变量的指针。当调用pthread_cond_signal时一个在相同条件变量上阻塞的线程将被解锁。如果同时有多个线程阻塞,则由调度策略确定接收通知的线程(一次解锁一个)。如果调用pthread_cond_broadcast,则将通知阻塞在这个条件变量上的所有线程。一旦被唤醒,线程仍然会要求互斥锁。如果当前没有线程等待通知,则上面两种调用实际上成为一个空操作。如果参数*cond指向非法地址,则返回值EINVAL。
参数:
	传入地址
返回值:成功返回0,出错返回错误编号。

2.5 示例:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unidtd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/

void *thread1(void *);
void *thread2(void *);

int i=1;
int main(void)
{
    pthread_t t_a;
    pthread_t t_b;

    pthread_create(&t_a,NULL,thread2,(void *)NULL);
    pthread_create(&t_b,NULL,thread1,(void *)NULL); 
    pthread_join(t_b, NULL);/*等待进程t_b结束*/
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    exit(0);
}

void *thread1(void *junk)
{
    for(i=1;i<=9;i++)
    {
        pthread_mutex_lock(&mutex);/*锁住互斥量*/
        if(i%3==0)
             pthread_cond_signal(&cond);/*条件改变,发送信号,通知t_b进程*/
        else       
             printf("thead1:%d/n",i);
        pthread_mutex_unlock(&mutex);/*解锁互斥量*/

		sleep(1);
	}

}

void *thread2(void *junk)
{
    while(i<9)
    {
        pthread_mutex_lock(&mutex);

		if(i%3!=0){
         	pthread_cond_wait(&cond,&mutex);/*等待,同时释放锁*/
     	}
        printf("thread2:%d/n",i);
        pthread_mutex_unlock(&mutex);

	sleep(1);
	}

}

运行结果

thread1:1

thread1:2

thread2:3

thread1:4

thread1:5

thread2:6

thread1:7

thread1:8

thread2:9

3 系统移植

4 指针&字符串

4.1 请问运行Test 函数会有什么样的结果

//题目 
void GetMemory(char *p)//错误的原因:把主函数中str中的值给了p,p指向的空间对str没有影响。
 {
    p = (char *)malloc(100);//没有修改str指向的内容
 }

  void Test(void)
 {
   char *str = NULL;
   GetMemory(str);
   strcpy(str, "hello world");
   printf(%s\n”,str);
}
答案:

		Segmentation fault (core dumped)
//修改
void GetMemory(char **p)//p指向了str的地址,*p为str,可以修改str中的值
 {
    *p = (char *)malloc(100);
 }

  void Test(void)
 {
   char *str = NULL;
   GetMemory(&str);
   strcpy(str, "hello world");
    printf(%s\n”,str);}

5 关键词

5.1 static

将修饰的对象放在静态区

1 修饰变量

  • 静态全局变量

    作用域:在本文件中,从定义处开始到文件按结束。

  • 静态局部变量

    ​ 在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他函数也用不了。由于被 static 修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。

2 修饰函数

​ 函数前加 static 使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

​ 如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可。

5.2 const

  • 修饰变量:表示该变量只读,并且该变量需要赋初值

  • 将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义.

    (1)const 修饰的只读变量具有特定的数据类型,而宏没有数据类型编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查
    (2)编译器通常不为普通const只读变量分配存储空间,而是将它们保存在符号表中,这使他成为一个编译期间的值,没有了存储与读内存的操作,使它的效率更高

5.3 signed与unsigned

int char 默认为有符号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYqc4hCy-1575876882905)(C:\Users\Dennis\AppData\Roaming\Typora\typora-user-images\1570711122563.png)]

  1. 补码

    正数的补码与其原码一致;

    负数的补码:符号位为 1,其余位为该数绝对值的原码按位取反,然后整个数加 1。

    补码求原码:如果补码的符号位为“1”,表示是一个负数,求原码的操作可以是:符号位为1,其余各位取反,然后再整个数加1。

  2. 注意

    char 数据类型可以存放数值

    int数据类型可以存放字符

    strlen() 函数与数值0或’\0’,计算其之前的字符个数。

intmain()
{
	char a[1000];
	int i;
	for(i=0; i<1000; i++)
	{
		a[i] = -1-i;
	}
	printf("%d",strlen(a));
	return 0;
}
结果:255

5.4 volatile

​ volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出 i
的值 。

5.5 typedef

​ 给一个已经存在的 数据类型(注意:是类型不是变量)取一个别名。

6 内存分布

  1. 栈(stack):由编译器进行管理,自动分配和释放,存放函数调用过程中的各种参数、局部变量、返回值以及函数返回地址。操作方式类似数据结构中的栈。

  2. 堆(heap):用于程序动态申请分配和释放空间。C语言中的malloc和free,C++中的new和delete均是在堆中进行的。正常情况下,程序员申请的空间在使用结束后应该释放,若程序员没有释放空间,则程序结束时系统自动回收。注意:这里的“堆”并不是数据结构中的“堆”。

  3. 全局(静态)存储区:分为DATA段和BSS段。DATA段(全局初始化区)存放初始化的全局变量和静态变量;BSS段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运行结束时自动释放。其中BBS段在程序执行之前会被系统自动清0,所以未初始化的全局变量和静态变量在程序执行之前已经为0。

  4. 文字常量区:存放常量字符串。程序结束后由系统释放。

  5. 程序代码区:存放程序的二进制代码。

    ​ 显然,C语言中的全局变量和局部变量在内存中是有区别的。C语言中的全局变量包括外部变量和静态变量,均是保存在全局存储区中,占用永久性的存储单元;局部变量,即自动变量,保存在栈中,只有在所在函数被调用时才由系统动态在栈中分配临时性的存储单元.

7 IO多路复用

7.1 select、poll和epoll的区别[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VjvtDGot-1575876882908)(C:\Users\Dennis\Desktop\v2-e6a869884585625dfc7eace1b90c3024_r.jpg)]

8 SPI总线

8.1 SPI的四种通信模式

在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。
时钟极性CPOL : 设置时钟空闲时的电平
​ 当CPOL = 0 ,SCK引脚在空闲状态保持低电平;
​ 当CPOL = 1 ,SCK引脚在空闲状态保持高电平。
时钟相位CPHA :设置数据采样时的时钟沿
​ 当 CPHA=0 时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
​ 当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

8.2 数据传输

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iknVx6Kq-1575876882909)(C:\Users\Dennis\Desktop\0_1319292061WTsN.gif)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1slG6BLJ-1575876882910)(C:\Users\Dennis\Desktop\复习计划(927)]

img

9 时间复杂度

算法的时间复杂度,用来度量算法的运行时间,记作: T(n) = O(f(n))。它表示随着 输入大小n 的增大,算法执行需要的时间的增长速度可以用 f(n) 来描述。

  1. 我们知道常数项对函数的增长速度影响并不大,所以当 T(n) = c,c 为一个常数的时候,我们说这个算法的时间复杂度为 O(1);如果 T(n) 不等于一个常数项时,直接将常数项省略。
  2. 我们知道高次项对于函数的增长速度的影响是最大的。n^3 的增长速度是远超 n^2 的,同时 n^2 的增长速度是远超 n 的。 同时因为要求的精度不高,所以我们直接忽略低此项。
  3. 因为函数的阶数对函数的增长速度的影响是最显著的,所以我们忽略与最高阶相乘的常数。

10 盲区题

main()
{
	int a = -1, b = 1, k;
	if(++a<0) && !(b--<=0)		//++a<0 不成立,则后面的不在执行
		printf("%d %d\n", a, b);
	else
		printf("%d %d\n", a, b);//执行该条语句,

}

11 函数作用域限定符

void func(void)
{//函数开始标识符
    
}//函数结束标识符

12 Shell脚本调试技术

https://www.ibm.com/developerworks/cn/linux/l-cn-shell-debug/

13 GDB调试段错误

https://blog.csdn.net/deutschester/article/details/6739861

14 char

unsigned char array[255],i;
for(i=0;i<255;i++)
    array[i]=i; //当i=255在+1会变为0,永远满足条件,无限循环

15 位操作反转

unsigned char reverse(unsigned char num)
{
    int ret= 0;
    for(int i= 0;i<8;i++)
    {
        ret = ret<<1;
        ret = ret|(num&0x01);
        num = num>>1;
    }
    return ret;
}

16 递归运用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3aPPxPcQ-1575876882912)(C:\Users\Dennis\AppData\Roaming\Typora\typora-user-images\1571056528478.png)]

#include<stdio.h>
#if 1
float add(float n)
{
    if(n < 0){
        return 0;
    }
    if(n == 1){
        return 1;
    }
    return (n + add(n-1)); //重点,n+ n-1 +…… +1
}
float del(float n)
{
    return (n/add(n));
}
int main(int argc, char const *argv[])
{
    float n;
    float sum = 0;
    int i;
    scanf("%f",&n);
    for(i=1; i<=n;i++)
    {
        sum = sum + del(i);
    }
    printf("sum:%f\n",sum);
    getchar();
    getchar();
    return 0;
}

17 宏定义

#define sum 32

​ 只是进行字符的替换,不会进行类型安全检查

#define sum(x) (x) + (x)

​ 宏函数:当宏函数被调用时只是以实参代替形参,不会值传递

18 互斥锁

作用:保护临界资源,保证在同一时刻只有一个任务访问临界资源

操作四步骤:

  1. 初始化锁:pthread_mutex_t mutex; pthread_mutex_init(&mutexNULL);
  2. 上锁:pthread_mutex_lock(&mutex);
  3. 解锁:pthread_mutex_unlock(&mutex);
  4. 销毁:pthread_mutex_destory(&mutex);

19 C++类

19.1 类成员函数的重载、覆盖和隐藏区别?

类成员函数的重载
(1)具有相同的作用域(即同一个类定义中);
(2)函数名字相同
(3)参数类型,顺序 或 数目不同(包括const参数和非const函数)
(4)virtual关键字可有可无。
C++成员函数的覆盖
(1)不同的作用域(非别位于派生类和基类中);
(2)函数名称相同
(3)参数列表完全相同;
(4)基类函数必须是虚函数。
隐藏是指派生类的成员函数遮蔽了与其同名的基类成员函数,具体规则如下:
(1) 派生类的函数与基类的函数同名,但是参数列表有所差异。此时,不论有无virtual关键字,基类的函数在派生类中将被隐藏。(注意别与重载混合)
(2)派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有virtual关键字。此时,基类的函数在派生类中将被隐藏。(注意别与覆盖混合)

19.2 已知String类定义如下,请尝试写出类的成员函数实现

class String
{
	public;
	String(const char *str = NULL); //通用构造函数
	String(const String &anather);  //拷贝构造函数
	~String();                      //析构函数
	吗oprater = (const String &rhs); //赋值函数
	private;
	char *m_data;    //用于保存字符串
}
//通用构造函数
String::String(const char *str)
{
   if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断
     {
       m_data = new char[1] ;
       m_data[0] = '\0' ;
     }
   else
    {
       m_data = new char[strlen(str) + 1];
       strcpy(m_data,str);
    }
} 
//拷贝构造函数
String::String(const String &another)
{
    m_data = new char[strlen(another.m_data) + 1];
    strcpy(m_data,other.m_data);
}
//赋值函数
String& String::operator =(const String &rhs)
{
    if ( this == &rhs)
        return *this ;
    delete []m_data; //删除原来的数据,新开一块内存
    m_data = new char[strlen(rhs.m_data) + 1];
    strcpy(m_data,this.m_data);
    return *this ;
}

//析构函数
String::~String()
{
    delete []m_data ;
}

19.3 C++中调用C编译器编译的函数为什么要写extern “c” 声明

答:

​ 首先,extern是C/C++语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。extern "C"是连接声明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与c语言的不同。例如,假设某个函数的原型为:void foo( int x, int y );该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。

19.4 计算内存大小

class MyClassA
{
	public:
		MyClassA(){}
		int calc{return (a+b);}
	private:
		int a;
		int b;
};

class MyClassB
{
	public:
		MyClassB(){}
		virtual calc(){return (a+b);}
	private:
		int a;
		int b;
};
sizeof(MyClassA) = 8  //没有虚函数,类的字符长度就是成员变量的长度
sizeof(MyClassB) = 12 //有一个虚函数,需要有一个4字节的空间存放void *类型的指针,指向一个存放虚函数地址的表中

19.5 引用与指针有什么区别

本质:引用是别名,指针是地址

1. 从现象上看,指针在运行时可改变其所指向的值,而引用一旦和某个对象绑定后就不在改变。
2. 从内存上分配看,程序为指针变量分配内存区域,而不用为引用分配内存区域,引用声明时必须初始化,从而指向一个已经存在的对象,引用不能指向空值。
3. 从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值是指针变量的地址值,而引用在符号表上对应的地址值是引用对象的地址值。

20 有序链表合并–递归

调用函数递推,达到终止条件回归

typedef struct node{
    int data;
    struct node *next;
}link;

link *link_merge(link *h1, link *h2)
{
    if(h1 == NULL){
        return h2;
    }else if(h2 == NULL){
        return h1;
    }else if(h1->data <= h2->data){
        h1->next = link_merge(h1->next,h2);
        return h1;
    }else{
        h2->next = link_merge(h1,h2->next);
        return h2;
    }
} 

21 字符串

#include<stdio.h>
char *getmemory(void)
{
    // char p[] = "hello world"; ----p 事实上指向是数组变量的首地址,当函数执行结束,数组变量销		//毁,相当于 char c[] = "hello"; char *p = c;
    char *p = "hello linux";
    return p;
}
int main(int argc, char const *argv[])
{
    printf("%s\n",getmemory());
    getchar();
    return 0;
}

22 平衡二叉树???

23 信号量互斥锁的区别

  • 信号量是用在多线程多任务同步的,互斥锁是用在多线程多任务互斥的

  • 信号量可以做到一个线程完成了某个动作就通过信号量告诉别的线程,别的线程再进行某些动作。

  • 互斥锁是指一个线程使用某个资源通过对其加锁而使得其他线程无法访问,直到这个线程解锁,其他线程才可以继续访问。

24 Linux环境使用命令

24.1 查看ip地址及接口信息?

$ifconfig

24.2 查看磁盘,大小?

$df -lh    -------查看磁盘
$ls -l filename --查看文件大小
$du -sh    -------查看当前文件夹大小

24.3 什么命令查看内存使用情况?

$cat /proc/meminfo

24.4 什么命令查看cpu占用率

$sudo apt-get install htop
$htop

25 实现一个求前100个质数的和的函数

#include<stdio.h>
#include<math.h>

int check_prime(int n)
{
   int k=sqrt(n+1);
   for(int i=2;i<=k;i++){
   	if(n%i==0){
   	 	return 0;
    }
   }
  return 1;
}

int add_prime(int num)
{
   int sum = 0;
   for(int i=2;i<=n;i++){
      if(check_prime(i)){
           sum+=i;
      }
   }
   return sum;
}

//2、3、5、7、11、13、17、19、23、29、31、37、41、43、47、53、59、61、67、71、73、79、83、89、97 100

26 程序什么时候应用使用线程,什么时候单线程效率高?

​ 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力 。
​ 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

27 将一个变量定义两次

int i = 10, j = 10, k = 3, k *= i+j; //将K定义了两次,编译出错。
//k最后的值是?
//程序无法顺利运行
//修改
int i = 10, j = 10, k = 3;
    k *= i+j; 
最终等于60

28 ICMP是什么协议?处于哪一层?

​ ICMP(Internet Control Message Protocol)Internet控制报文协议,工作在网络层
​ 它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。

29 IP组播有哪些好处

​ Internet上产生的许多新的应用,特别是高带宽的多媒体应用,带来了带宽的急剧消耗和网络拥挤问题。组播是一种允许一个或多个发送者(组播源)发送单一的数据包到多个接收者(一次的,同时的)的网络技术。组播可以大大的节省网络带宽,因为无论有多少个目标地址,在整个网络的任何一条链路上只传送单一的数据包。所以说组播技术的核心就是针对如何节约网络资源的前提下保证服务质量。

30 求结构体定义变量的起始地址和成员的偏移地址

// testConsole.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

 //重点*****************************************************************************
#define offset_of(type, field) ( (unsigned int*)&(((type *)(0))->field) )//把0作为结构体的起始地址求成员的偏移地址。
#define container_of(ptr, type, field) (type *)((char *)ptr - offset_of(type, field))//用成员变量在内存中分配到的内存减去偏移地址就是结构体的首地址。

typedef struct{
    int     a;
    int     b;
    char    c;
    int     d;
}package_t;
 
int main()
{
    package_t pkg;
    int *p;
 
    p = (int *)&pkg.d;
    printf("offset of 'd' is %d.\n", offset_of(package_t, d));
    printf("The addr of pkg = 0x%x.\n", &pkg);
    printf("The addr of d's container = 0x%x.\n", container_of(p, package_t, d));
    return 0;
}
 

31 volatile

volatile:告诉编译器不要优化
int *volatile reg:reg指针指向的内存内容每次从内存读取
int volatile *reg,:每次从内存读取reg的值,即reg的指向
volatile int *volatile reg:reg指针指向的内存内容每次从内存读取和reg的指向都从内存读取

#define offset_of(type, field) ( (unsigned int*)&(((type *)(0))->field) )//把0作为结构体的起始地址求成员的偏移地址。
#define container_of(ptr, type, field) (type *)((char *)ptr - offset_of(type, field))//用成员变量在内存中分配到的内存减去偏移地址就是结构体的首地址。

typedef struct{
int a;
int b;
char c;
int d;
}package_t;

int main()
{
package_t pkg;
int *p;

p = (int *)&pkg.d;
printf("offset of 'd' is %d.\n", offset_of(package_t, d));
printf("The addr of pkg = 0x%x.\n", &pkg);
printf("The addr of d's container = 0x%x.\n", container_of(p, package_t, d));
return 0;

}


## 19 volatile

```c
volatile:告诉编译器不要优化
int *volatile reg:reg指针指向的内存内容每次从内存读取
int volatile *reg,:每次从内存读取reg的值,即reg的指向
volatile int *volatile reg:reg指针指向的内存内容每次从内存读取和reg的指向都从内存读取
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值