C语言笔记8:联合体、文件操作、宏等

发布于:2025-07-02 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

联合体中的内存分配方式:

使用联合体判断机器的字节序:

字符串字面量

文件操作函数:

预处理、编译、链接:

预定义宏:

offsetof宏:

头文件不能定义全局变量:

循环包含问题:


联合体中的内存分配方式:

#include<stdio.h>
int main()
{
  union
  {
    short k;
    char i[2];
  }*s, a;
  s = &a;
  s->i[0] = 0x39;
  s->i[1] = 0x38;
  printf("%x\n", a.k);
  return 0;
}

上述代码的打印结果是:

由于联合体是所有成员变量共用一块内存空间,并且每次只会使用其中一个成员变量.

而因为联合体中,成员变量的起始地址相同,所以打印的结果和机器的字节序有关,

在内存中的存储情况为:

低地址->高地址:39 38

而按照小端字节序的方式打印的话:就是3839。

使用联合体判断机器的字节序:

int check_sys()
{
	union {
		int a;
		char b;
	}u1;
	u1.a = 1;
	return u1.b;
}
int main()
{
	/*char str[] = "-3913abf";
	int ret = my_atoi(str);
	printf("%d\n", ret);*/
	int ret = check_sys();
	if (ret == 0)
	{
		printf("大端\n");
	}
	else {
		printf("小端\n");
	}

	return 0;
}

字符串字面量

char *GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

对于上述代码,GetMemory函数中的p指针指向一个"hello world"字符数组(调用函数时,这个数组存储在栈上),而这个字符数组其实是拷贝了字符串字面量("hello world")(存储在只读代码段),所以返回的p指针指向一个不可使用的空间。

文件操作函数:

【C语言】文件操作函数_fopen函数的用法-CSDN博客

预处理、编译、链接:

预处理只会处理#开头的语句,编译阶段只校验语法,链接时才会去找实体

预处理:相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有头文件(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。

编译:将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,不负责寻找实体。

链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。

预定义宏:

预先定义好的宏,它们提供了关于编译环境、代码位置、编译器特性等信息

  1. __FILE__

    • 作用:展开为当前源文件的字符串字面量

    • 示例:printf("当前文件:%s\n", __FILE__);

  2. __LINE__

    • 作用:展开为当前行号的整型常量

    • 示例:printf("当前行号:%d\n", __LINE__);

  3. __DATE__

    • 作用:展开为编译日期的字符串 ("MMM DD YYYY"格式)

    • 示例:printf("编译日期:%s\n", __DATE__);

  4. __TIME__

    • 作用:展开为编译时间的字符串 ("HH:MM:SS"格式)

    • 示例:printf("编译时间:%s\n", __TIME__);

  5. __FUNCTION__:展开为当前执行的函数名。

#include <stdio.h>

void my_function() {
    printf("正在执行函数: %s\n", __FUNCTION__);
    // 其他代码...
}

int main() {
    my_function();
    return 0;
}
正在执行函数: my_function

.......

offsetof宏:

传入两个参数:结构体类型名和该成员变量名,offsetof宏就能计算该成员变量在结构体中相对于首地址的偏移量。

#define offsetof(StructType, MemberName) (size_t)&(((StructType *)0)->MemberName)

将一个整数的二进制位的奇数位和偶数位交换

#define SwapIntBit(n) (((n) & 0x55555555) << 1 | ((n) & 0xaaaaaaaa) >> 1)

首先n按位与上01010101...01(假设低位的第一位是‘1’,奇数位),这样就取出了奇数位的数字,再将其左移一位,奇数位的数字就全部来到了偶数位。

然后n按位与上10101010...10,这样就取出了偶数位的所有数字,再右移一位,让偶数位的数字来到奇数位。再使用按位或,将这两个(奇数位的数字移动到偶数位,偶数位的数字移动到奇数位)二进制数加起来。就得到了奇偶互换的效果。

头文件不能定义全局变量:

头文件中直接定义全局变量确实会导致链接时出现多重定义错误

在头文件中直接定义全局变量时:

// globals.h
int global_var = 42;  // 直接定义

如果这个头文件被多个源文件包含,每个源文件都会有一份global_var的定义,导致链接器发现多个同名全局变量定义,产生冲突。

应该和函数一样,在头文件中声明,在源文件中定义:

// globals.h
extern int global_var;  // 只是声明,不分配内存

// globals.c
#include "globals.h"
int global_var = 42;    // 实际定义,分配内存

或者使用static的方法:

// globals.h
static int file_local_var = 42;  // 每个包含的文件有独立副本

使用这种方法,每个源文件都有一个独立的同名变量,但是在该源文件中对这个变量的修改,不会影响到其他源文件中该名字变量的值。

循环包含问题:

// a.h
#include "b.h"

// b.h
#include "a.h"  // 循环包含

尽量避免头文件的循环包含。