learn_C_deep_10 extern在多文件下的理解、struct 关键字的理解与柔性数组、union 的内存级布局理解、enum 关键字的基本理解、typedef 的理解与分类、关键字总结

简介: learn_C_deep_10 extern在多文件下的理解、struct 关键字的理解与柔性数组、union 的内存级布局理解、enum 关键字的基本理解、typedef 的理解与分类、关键字总结

extern 在多文件下的理解与使用


       extern 是一个关键字,用于在程序中声明外部变量或函数。

变量的声明通常包含变量的类型和名称,但是如果变量定义在一个源文件中,而其它源文件也需要访问这个变量,则需要在其它源文件中声明该变量。此时可以使用 extern 关键字来告诉编译器,该变量已经在其它文件中定义好了,不需要再重新分配存储空间,只需要使用它即可。


例如:

在编译 file2.c 文件时,由于该文件中使用了 extern 关键字声明了变量 x,所以编译器不会分配新的存储空间,而是使用 file1.c 中已经定义好的变量 x。 函数的情况类似,如果一个函数定义在一个源文件中,而其它源文件也需要调用该函数,则需要使用 extern 关键字来声明该函数。


例如:

在编译 main.c 文件时,由于该文件中包含了 func.h 头文件,其中使用了 extern 关键字声明了函数 func,所以编译器知道该函数已经在其它源文件中定义好了,可以直接在链接时链接函数即可。


struct 关键字的理解与柔性数组


       在C语言中,struct是一个关键字,用于定义结构体。结构体是由多个不同类型的数据组成的集合,可以将它们组合成一个自定义的数据类型。结构体可以包含不同的数据类型,如整型、浮点型、字符型、数组以及其他结构体等,它们可以组合成复杂的数据结构。


例如学生:

 这段代码定义了一个包含姓名、年龄、性别和体重的学生结构体Stu,并在main函数中定义了一个Stu类型的变量s,并为其赋值。接着定义了一个指向Stu结构体的指针p,并将其指向变量s的内存地址。最后,通过指针p访问结构体中的成员变量,并将它们打印出来,分别使用了结构体指针的两种访问方式:(*p).name 和 p->name,它们的作用是一样的。


空结构体的大小



这段代码中定义了一个名为S的结构体,但是结构体里面没有任何成员。在代码中调用了sizeof操作符,对结构体S进行计算其大小的操作,但vs编译器在编译这段代码时会报错。根据C语言的规定,定义一个结构体或者联合体时,至少需要包含一个成员,否则这个结构体是无效的。


  这段代码中定义了一个名为S的结构体,但是结构体里面没有任何成员。在代码中调用了sizeof操作符,对结构体S进行计算其大小的操作,但gcc编译器在编译这段代码时不会报错,求得大小为0,并且可以定义变量(可以定义空间大小为0的变量),但是没有空间,不能进行初始化。


柔性数组


#include<stdio.h>
#include<stdlib.h>
struct S
{
  int num;
  int arr[];
};
int main()
{
  printf("%d\n", sizeof(struct S));//柔性数组不占用空间
  struct S* p = malloc(sizeof(struct S) + sizeof(int) * 10);
  int i = 0;
  p->num = 10;
  for (i = 0; i < p->num; i++)
  {
    p->arr[i] = i;
  }
  free(p);
  p = NULL;
  return 0;
}


union 的内存级布局理解


 在C语言中,union是一种特殊的数据类型,它允许您在同一内存位置存储不同的数据类型。它类似于结构体,但是它的所有成员共享同一内存空间,因此只能够保存其中一个成员的值。 定义一个union变量的语法与结构体相似,


例如:


这个union类型变量un可以保存整数int、字符char。但是,不能同时保存这两种类型,因为它们共享同一个内存空间。 Union的大小由其最大成员的大小确定,例如上面的例子中,un变量的大小是4字节,因为整形类型的成员是这个联合体中最大的。


基本认识与补充认识



union的内存布局



大小端对于union的影响



小练习

//联合体内部的成员起始地址都是一样的!!!
#include<stdio.h>
union un {
  int a;
  char c[4];
}*p,u;//全局变量*p、u
int main()
{
  p = &u;
  p->c[0] = 0x39;
  p->c[1] = 0x38;
  p->c[2] = 0x37;
  p->c[3] = 0x36;
  printf("0x%x\n", p->a);
  return 0;
}


enum 关键字的基本理解



enum 与 #define 的区别



typedef 的理解与分类



typedef 与 #define 的区别


1. 定义方式不同

       - typedef 是一种类型定义,用于给已有类型取一个新的名字,是在编译期间解析。

- #define 是一种预处理编译指令,用于替换代码中的文本宏,是在预处理阶段解析,即在编译器处理代码之前。


2. 定义的作用域不同

       - typedef 定义的作用域是局部的,只在当前定义的作用域内有效。

       - #define 定义的作用域是全局的,从定义位置开始到文件末尾都有效。


3. 可读性和维护性不同

       - typedef 的定义会产生一个新的类型名,使得代码更容易理解和维护。

       - #define 定义的宏不会改变原有的类型或者结构体,容易引起混淆和错误。


4. 可以对已有类型(包括自定义类型)进行重定义

       - 使用 typedef 语句,可以为 C 语言中已有的类型定义一个新的名字。

       - #define 指令无法直接对 C 语言中已有的类型进行重定义。


总的来说,typedef 适用于定义新的数据类型或别名,而 `#define` 适用于定义常量和函数宏等。

//问题1:
typedef int * ptr_t;
ptr_t p1,p2;
问:p1,p2分别是什么类型
//问题2:
#define PTR_T int*
PTR_T p1, p2;
问:p1,p2分别是什么类型


问题1:p1和p2分别是指向int类型的指针变量。由于typedef int * ptr_t; 定义了一个名为ptr_t的类型,它是指向int类型的指针类型,因此ptr_t p1,p2; 相当于 int* p1, p2; 即p1和p2都是指向int类型的指针变量。

问题2中,#define PTR_T int* 定义了一个名为PTR_T的宏,将int*替换为PTR_T。因此 PTR_T p1, p2; 相当于 int* p1, p2; 这里,p1是指向int类型的指针变量,而p2是int类型的普通变量。但是这种定义方式可能会带来理解上的混淆,因为它让人很难区分p1和p2的类型,不建议使用。正确的做法是使用typedef类型定义来明确变量的数据类型。

#include <stdio.h>
#define INT32 int
typedef int int32;
int main()
{
  //unsigned int32 a = 10; C中typedef不支持这种类型的扩展,不能当成简单的宏替换
  unsigned INT32 a = 20; //宏简单替换,可以
  return 0;
}

   在typedef中,INT32类型仅被视为一种新的数据类型,C中typedef不支持这种类型的扩展,不能当成简单的宏替换,而在宏定义中,INT32被视为简单的字符替换。


关键字总结


数据类型关键字(12个)



控制语句关键字(12个)



存储类型关键字(5个)



注意:存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个。


其他关键字(3个)



现在我们来看看这个有没有问题

typedef static int s_int;


error C2159: 指定了一个以上的存储类,因为static和typedef都是存储类型关键字,而存储类型关键字不可以同时出现,所以不能这样写代码。

相关文章
|
4月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
422 55
|
6月前
|
分布式计算 算法 Java
|
9月前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
292 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
9月前
|
机器学习/深度学习 人工智能 缓存
【AI系统】推理内存布局
本文介绍了CPU和GPU的基础内存知识,NCHWX内存排布格式,以及MNN推理引擎如何通过数据内存重新排布进行内核优化,特别是针对WinoGrad卷积计算的优化方法,通过NC4HW4数据格式重排,有效利用了SIMD指令集特性,减少了cache miss,提高了计算效率。
283 3
|
11月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
185 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
11月前
|
存储 Java
深入理解java对象的内存布局
这篇文章深入探讨了Java对象在HotSpot虚拟机中的内存布局,包括对象头、实例数据和对齐填充三个部分,以及对象头中包含的运行时数据和类型指针等详细信息。
94 0
深入理解java对象的内存布局
|
存储 算法 Oracle
不好意思!耽误你的十分钟,JVM内存布局还给你
先赞后看,南哥助你Java进阶一大半在2006年加州旧金山的JavaOne大会上,一个由顶级Java开发者组成的周年性研讨会,公司突然宣布将开放Java的源代码。于是,下一年顶级项目OpenJDK诞生。Java生态发展被打开了新的大门,Java 7的G1垃圾回收器、Java 8的Lambda表达式和流API…大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
不好意思!耽误你的十分钟,JVM内存布局还给你
|
11月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
296 0
|
11月前
|
缓存 Java 编译器
【多线程-从零开始-伍】volatile关键字和内存可见性问题
【多线程-从零开始-伍】volatile关键字和内存可见性问题
134 0
|
11月前
|
程序员 Windows
程序员必备文件搜索工具 Everything 带安装包!!! 比windows自带的文件搜索快几百倍!!! 超级好用的文件搜索工具,仅几兆,不占内存,打开即用
文章推荐了程序员必备的文件搜索工具Everything,并提供了安装包下载链接,强调其比Windows自带搜索快且占用内存少。
273 0