C语言-内存管理、输入输出缓冲区、字符串

一、C语言的堆内存管理:

堆内存:

是进程的一个内存段(textdatabssheapstack),由程序员手动管理。
特点就是足够大,缺点就是使用麻烦,比较危险。

使用堆内存的原因

  • 随着程序变复杂,数据量开始变多。
  • 其它内存段的申请和释放不受控制,堆内存的申请释放受程序员控制。

堆内存的使用:C语言中没有管理堆内存的语句,C标准库中提供一套管理堆内存的函数,这些函数底层封装了各操作系统的堆内存管理接口,所以可以跨平台使用,这些函数声明在 stdlib.h 头文件中。

malloc函数:
/**
 * 功能:向malloc申请连续的size字节的堆内存块
 * @size:
 *    要申请的内存块字节数
 *    如果申请数组形式的内存块,size=sizeof(数组元素类型)*数组长度
 * 返回值:
 *	  如果申请成功,则返回内存块首地址,绝大多数情况是成功
 *    失败申请失败,则返回NULL,例如现在有堆内存无法满足size个字节的需求
 */
void *malloc(size_t size);

注意:
1、使用malloc申请到的内存块,里面的内容是不确定的,malloc不会帮我们初始化,可以使用bzero, memset函数进行初始化。
2、如果size等于0,返回NULL或唯一个的地址,并且该地址可以通过free释放而不出错,但不能使用它指向的内存。
3、通过一次malloc申请出来的内存,如果成功时该内存段必定连续,如果分多次malloc申请多段内存段,每段内存段之间不一定连续

calloc函数:
/**
 * 功能:申请nmemb个size个字节的内存块,专门用于申请数组型的内存块。
 * @nmemb:数组的长度
 * @size:数组元素的字节数
 * 返回值:与malloc相同
 * 注意:使用calloc申请的内存块,所有字节会被初始化0。
 */
void *calloc(size_t nmemb, size_t size);

注意:
1、calloc所申请也是一块连续的内存块,所以nmembsize的参数位置可以调换,就相当于calloc内部调用了malloc函数,只是比malloc多了初始化步骤,而且比malloc的可读性更高。
2、malloccalloc申请内存的速度快,或者使用malloc + bzero配合。

free函数:
/**
 * 功能:释放堆内存 
 * @ptr:要释放的内存块的首地址,它必须是malloc、calloc函数的返回值
 */
void free(void *ptr);

注意:
1、free释放的是使用权,只破坏内存块的一部分内存,大部分数据还在,这样设计的原因是释放速度比较快,就像在硬盘上删除文件一样,只是把存储文件那片区域的使用释放旧,数据还存储在磁盘上,这也是我们能进行数据恢复的原因。
2、free的参数可以是空指针,不会出现错误,也不会执行任何操作,这也是空指针比野指针安全的原因。
3、如果内存被重复释放则会出现double free or corruption (fasttop),程序会异常停止,所以在第一次释放内存后,要把与堆内存配合的指针及时的赋值为空,防止重复释放产生的错误。

realloc函数:
/**
 * 功能1:把已有的堆内存块调小
 * ptr是malloc、calloc、realloc的返回值,也就已有堆内存块首地址
 * size < oldsize 此时不需要关心realloc的返回值
 */

/**
 * 功能2:把已有的堆内存块调大
 * ptr是malloc、calloc、realloc的返回值,也就已有堆内存块首地址
 * 情况1:如果ptr后续的内存没有被占用,realloc会在ptr的基础上进行扩大
 * 情况2:如果ptr后续的内存已经被占用,realloc会重新分配一块符合要求的内存块,并把ptr上的内容拷贝到新的内存块,然后释放ptr,再返回新内存块的首地址
 * 使用此功能时,我们必须重新接收realloc函数的返回值,我们无法预料realloc执行的是情况1还是情况2。
 */

/** 
 * 功能3:释放内存
 * ptr是malloc、calloc、realloc的返回值
 * 0==size,此时realloc的功能就相当于free
 */

/** 
 * 功能4:申请内存
 * ptr == NULL , size > 0, 此时的功能就相当于malloc
 */
void *realloc(void *ptr, size_t size);

注意:虽然realloc具有释放和申请堆内存的功能,但我们一般不使用,而是直接使用mallocfree,主要使用的是realloc的调整堆内存块大小的功能。
总结: 使用堆内存只需要掌握mallocfree函数即可,对于callocrealloc函数了解即可。

堆内存越界时为什么超过135160才会出现段错误?

#include <stdio.h>
#include <stdlib.h> 
int main() {
    char* ptr = malloc(1);
    // 只要越界的不超过135160,就不会出现段错误
    printf("%c\n", ptr[135160]);
    printf("%c\n", ptr[4096 * 33 - 9])
}

  1. 当程序首次向malloc申请内存时,此时malloc手里没有堆内存可分配,malloc会向操作系统申请堆内存,操作系统会一次性分配33页内存交给malloc管理(一页内存=4096个字节),之后再向malloc申请内存时,malloc会从这33页内存中分配给调用者。但这不意味着可以越界访问,因为malloc把使用分配给"其他人",这样会产生脏数据。
  2. ​使用malloc申请的每个内存块前面会有4~12个字节的空隙,malloc会根据所申请的内存块的大小自动调整空隙的大小。
  3. 内存块前面的空隙有两部分:
  • 空隙的前0~8字节:用于内存对齐(目的提高内存的访问速度)
  • 空隙的末尾4字节:也就是内存块前面的4字节,存储首malloc的管理信息,这块信息被破坏会影响后续mallocfreeprintfscanf函数的使用。

注意:堆内存越界的后果?
135160会出现段错误的原因:​操作系统交给malloc33页内存(135168个字节),可访问的范围是**0135167**,`malloc`会预留8个字节的空隙,返回给程序的是33页内存的第9个字节的地址(33页内存还剩135160个字节),所以可访问的范围是0135159,只要在这个范围就不会出现段错误。

优点:
1、避免了频繁打扰操作系统,而影响操作系统的速度。
2、段错误产生的原因是被操作系统发现非法使用内存,所以我们使用malloc分配的内存越界时,只要不超过33页范围就不会产生段错误。

使用堆内存越界的后果:

​ 1、越界使用的是空隙的空闲字节,一切正常,可以安全访问。

int main() {
	int* p = malloc(4);
    p[0] = 123; // 申请到的内存块
    p[1] = 456;	// 空闲
    p[2] = 789; // 空闲
}

​ 2、越界破坏了malloc的管理信息,会影响后续malloc、free、scanf、printf函数的使用。

int main() {
	int* p = malloc(4);
    p[3] = 0; 	// 存储着malloc的管理信息,后续无法继续申请堆内存
    p[-1] = 0;	// 存储着malloc的管理信息,p内存块无法释放
}

3、越界使用malloc还未分配出去的内存,虽然不会产生段错误,但后续malloc把它分配出去后,可能会产生脏数据。
4、超出33页范围,就产生段错误。

内存碎片:

​ 已经释放了使用权的内存,但无法被malloc再次分配出去,这种内存叫内存碎片。

int *p1 = malloc(4);
int *p2 = malloc(4);
int *p3 = malloc(4);

free(p2);

// 此时p2已经被释放,但无法再次分配给p4,p2就是内存碎片,如果后续不再分配较小的内存块,p2可能一直接内存碎片
int *p4 = malloc(16);

// 此时p2就有可能被再次分配出来,它就不是内存碎片,
int *p5 = malloc(4);

// 如果p1或p3被释放,p2就不是内存碎片了
free(p1);
free(p3);

内存碎片产生的原因:内存和释放、分配时间、大小不协调导致的。一块内存碎片,只是短时间内是碎片,过一段时间它可能就不是碎片了,所以内存碎片不是绝对的。

如何减少内存碎片:
​前提:内存碎片只能尽量减少,无法杜绝。
​1、尽量使用栈内存(要了解栈内存的特性,要知道栈内存的使用上限,ulimit -s 、ulimit -s )。
2、尽量分配大块内存自己管理。
3、按照分配的顺序,逆序释放,把堆内存当栈进行管理。
4、内存碎片整理

内存泄漏:

​ 内存已经不再使用,但无法被释放的内存叫内存泄漏。

void func(int num) {
    int *p = malloc(num);
    if (条件) return;
    free(p);
}

int main(void) {
    while (1) {
    	func(4);
    }
    free(p)
}

​ 但这不是最严重的,严重的是反复的内存泄漏,例如:一个函数执行需要分配一块堆内存,等它执行完毕后堆内存没有被释放,等再次调用这个函数时,它又重新分配堆内存,又没有释放,反复这样会导致可用的内存越来越少,系统、程序会变得越来越慢、卡、死机。
注意:程序一旦结束属于它的资源都会被操作系统回收。但不是所有程序都适用该方法来回收资源。

产生内存泄漏的原因:
1、只写的内存分配语句,而忘记写内存释放语句,可能是粗心大意,也可能是以为别人会释放。
2、写了内存释放语句,但由于执行流程、执行条件设计有问题,导致释放语句没有执行。
3、与堆内存配合的指针被破坏,改变了指向,导致free语句执行无效。

int *p = malloc(4);
*p = 100;
p = NULL;
free(p);

如何减少内存泄漏:

  1. 按规则分配、释放内存:
  • 自用:谁申请谁释放,分配语句和释放语句成对出现。
  • 共用:谁知道该释放谁释放,项目组中负责分配和负责释放的人要进行对接。
  1. 封装mallocfree函数,记录每一次的分配和释放的内存块地址,通过对比记录,就可以发现是否有内存泄漏。
  2. 使用const保护与堆内存配合的指针变量,防止指针被破坏。
void* myMalloc(size_t size) {
    void* ptr = malloc(size);
    printf("debug: my_malloc:%p\n",ptr);
    return ptr;
}

void myFree(void *ptr) {
    printf("debug: my_free:%p\n",ptr);	//	可以记录到日志
    free(ptr);
}
// 使用const保护与堆内存配合的指针变量,防止指针被破坏
int* const p = malloc(4);
p = NULL;	//	无法修改

如何判断和定位内存泄漏:

1、查看内存的使用情况

​ windows 任务管理器 、Linux ps -aux命令
​ 大致确定是哪个进程发生了内存泄漏

2、使用检查内存泄漏的工具:
sudo apt-get update #更新软件源
sudo apt install valgrind # 安装该工具的命令

# valgrind是一套Linux下的仿真调试工具集
# memcheck是其中一个工具,可以检查程序中的内存问题,如泄漏、越界、非法指针等。可以检测:
# 	使用未初始化的内存
# 	读/写已经被释放的内存
# 	读/写内存越界
# 	读/写不恰当的内存栈空间
# 	内存泄漏
# 	使用malloc和free不匹配等
		
#使用该工具检测程序:
valgrind --tool=memcheck --leak-check=yes ./a.out
	--tool=<name>指定要使用的工具,默认为memcheck
	--leak-check=yes|no	是否对内存泄漏给出详细信息
3、根据封装的malloc和free记录到日志的信息进行比对

内存泄漏和内存碎片的危害:

前提:当程序结束时,操作系统会把分配它的所有资源全部回收(包括系统分配给程序的堆内存),所以当程序结束时,内存碎片和内存泄漏就会消失,这也是为什么软件、系统重启能解决很多问题。
注意:服务器端的程序一般需要7*24小时运行,不能随意结束。
​ 客户端的程序可以随意的关闭、重启系统和软件,所以即使发生内存碎片和内存泄漏,也问题不大,但如果是服务端的程序,即使只有少量的内存泄漏和内存碎片,长年累月下来也会导致系统可用的内存越来越少,系统、程序会变得越来越慢、卡、死机。
说明想要共享指针变量,必须传递二级指针

void createMem(void** p, size_t n) {
    *p = malloc(n);
    printf("create_mem:%p\n",*p);
    if (NULL == *p) {
        printf("malloc error\n");
    }
}

int main() {
    int* p = NULL;
    createMem(&p,40);
    printf("--------%p\n",p);
    for (int i = 0; i < 10; ++i) {
        p[i] = i;
        //printf("--------\n");
        printf("%d%c", p[i], " \n"[i == 9]);
    }
    free(p);
    p = NULL;
}   
常用的内存操作函数:
void bzero(void *s, size_t n);
功能:把内存块s的n个字节,赋值为0void *memset(void *s, int c, size_t n);
功能:把内存块s的n个字节,赋值为c(0~255)
    
void *memcpy(void *dest, const void *src, size_t n);
功能:从src内存块拷贝n个字节的内容到dest内存块

void *memmove(void *dest, const void *src, size_t n);
功能:与memcpy相同,不同的是当dest与src重叠时,该函数能正常工作,memcpy行为不确定
    
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2内存块的n个字节
    s1 > s2 返回1
    s1 < s2 返回-1
    s1 == s2 返回0

二、缓冲区

输出缓冲区:

​ 当我们使用标准库的输出系列函数打印数据到屏幕,数据并不会立即显示到屏幕上,而先存储到一块内存中,我们把这块内存称为输出缓冲区,等满足相关条件后,再从缓冲区中显示到屏幕,相关条件有:

  1. 从输出状态切换到输入状态。
  2. 缓冲区满了,1k=1024个字节,系统会把缓冲区中所有数据一起显示到屏幕了。
  3. 程序正常结束时,系统会把缓冲区中所有数据一起显示到屏幕了。
  4. 遇到'\n'时,'\n'前面的数据会立即显示到屏幕上。
  5. 调用fflush(stdout)强制刷新,会把立即输出缓冲区中所有数据一起显示到屏幕了。

总结:缓冲区机制的目的是为了提高输入输出效率

输入缓冲区:

​ 当我们从终端输入数据给程序时,系统并没有立即把数据交给程序读取,而先存储到了一块内存中,我们这块内存称为输入缓冲区,直到我们按下Enter键时,系统才会把缓冲区中的数据给程序读取。
​ 当我们输入的数据过多,或者类型不匹配,标准的输入系列函数就会读取失败,或只读取一部分,剩余的数据就会残留缓冲区中,影响后续数据的输入,当我们发现这情况情况后,应先清理输入缓冲区,后续的数据才能正常输入

清理输入缓冲区的方式:

// 方法1:
while('\n' != getch());	//	清空缓冲区,直到按下回车结束

// 方法2:正则表达式
scanf("%*[^\n]");	//	从缓冲区中读取任意类型数据并丢弃,直到遇到'\n'
scanf("%*c");  		//	从换乘区中读取一个字符并丢弃

// 方法3:
stdin->_IO_read_ptr = stdin->_IO_read_end;	
//	设置输入缓冲区的位置指针到缓冲区末尾,此时缓冲区会被操作系统自动清空

注意:
1、方法3只能在Linux系统中使用
2、如果输入缓冲区中本来就没有垃圾数据,使用方法1和方法2就需要你手动多输入一个'\n'作为垃圾数据,程序才能往下走

三、字符串:

字符:

​ 字符就是符号或图案,但在计算机中以整数形式存在,当需要显示时,会根据ASCII表中的对应关系显示出相应的符号或图案。

​ 在C语言中使用char类型的变量存储字符的ASCII码值,也就是使用整数进行模拟字符,标准的ASCII码表的范围是:0 ~ 127,共128个字符,其他的语种,使用-128 ~ -1进行设计字符编码,比如中文的汉字,使用的是2~3字节存储一个汉字。

重要的字符:

'\0' ASCII值是 0  空字符  字符串的结束标志
'0' ASCII值是 48
'A' ASCII值是 65
'a' ASCII值是 97

输出:

printf("%c", ASCII值);
putchar(ASCII值);

字符的输入:

char ch;
scanf("%c",&ch);
ch = getchar();

注意:当先输入数值型数据(整数形、浮点型),再输入字符型数据时,前一次的输入会残留一个'\n'或空格,影响字符型数据的输入,是缓冲区在影响字符的输入
解决方法:

// 方法1:增加一个空白字符的接收函数
scanf("%*c");
getch();
getchar();

// 方法2:在%c前面增加一个空格
scanf(" %c");

// 方法3:全部清空输入缓冲区
stdin->_IO_read_ptr = stdin->_IO_read_end;
判断字符类型的函数:
函数名 函数功能
isalnum() 当字母或数字字符时, 返回真值
isalpha() 当字母字符时, 返回真值
iscntrl() 当控制字符时, 返回真值
isdigit() 当数字字符时, 返回真值
isgraph() 当非空格可打印字符时, 返回真值
islower() 当小写字母字符时, 返回真值
isprint() 当可打印字符时, 返回真值
ispunct() 当标点字符时, 返回真值
isspace() 当空格字符时, 返回真值
isupper() 当大写字母字符时, 返回真值
isxdigit() 当十六进制字符时, 返回真值

串型结构:

​ 由若干个相同类型的数据组成顺序表(数组),在数据的末尾有一个结束标志,在使用这种数组时,可以不关心数组的长度。并且串型结构的处理都是批量性的

#include <stdio.h>
void showString(int arr[]) {
    for (int i = 0; arr[i] != ~0; ++i) {   
        printf("%d ",arr[i]);
    }   
}
int main() {
    int arr[] = {33, 5, 0, 63, 34, 23, 5, ~0, 32, 23, 56};             
    showString(arr);
}

字符串:

​ 由字符类型组成的串型结构,它的结束标志是'\0',使用它可以存储单词、句子、文章、汉字等更丰富的信息,一般使用char类型的数组存储。

// 定义字符串时,要为'\0'预留位置
char arr1[] = {'H','e','l','l','o','\0'};
char arr2[10] = {'H','e','l','l','o'};
字符串字面值:
  1. "由双引号包括着的若干个字符"
  2. 它是以常量字符数组的形式存在,末尾隐藏着一个'\0'
  3. 它们会被存储在text内存段,一旦强行修改就会出现段错误。
  4. 使用指针指向字符串字面值时,一定要用const加以保护,防止出现段错误,宁可出现编译时的错误,也不要出现运行时的错误。
  5. 编译器会优化它的存储,相同的字符串字面值,只会存储一份在text内存段中。
  6. 最常用的是用它给字符数组初始化,char arr[] = "hello"编译器会自动拷贝字符串到数组的内存中(包括'\0'),完成初始化就有了两份字符串存储在内存中,一份存储在stack \ data,另一份还存储在text

注意:使用字符串字面值给字符数组赋值,只能在定义字符数组时使用,这是编译器帮忙完成拷贝的,在完成字符数组的定义后,只能使用strcpy函数对字符串进行赋值。

字符串的输出:

printf("%s",字符串的首地址);
puts(字符串的首地址); // 输出完字符串后会再输出一个\n

字符串的输入:

scanf("%s",存储字符串的首地址);
缺点:不能输入带有空格的字符串

char *gets(char *s);
返回值:就是s,为了链式调用
缺点:直接从终端中接收字符数据,遇到'\n',可以接收空格字符,但是它不检查数据的长度跟存储空间的关系,所以很容易接收过长产生段错误、脏数据,官方编译器不建议使用该函数,会产生警告

char *fgets(char *s, int size, FILE *stream);
功能:可以从指定文件stream中读取不超过size - 1个字符会自动在末尾添加'\0',并存储到s中,返回值也是s,为了链式调用
stream: 数据的来源,写stdin即可 stdout 一切皆文件
size:最多只能读取size-1个字符,必定会为'\0'预留位置
缺点1:如果输入的字符个数不足size - 1个时,会把最后输入的'\n'一起接收
缺点2:如果输入的字符个数超过size - 1个时,超出部分的字符数据会继续残留在输入缓冲区中,会继续影响后序的输入

解决方法:
char usr[6] = {};
printf("请输入字符串:");
fgets(usr, 6, stdin);

int len = -1;
//  计算出'\0'下标为len
while(usr[++len]);

//  检查'\0'前面是否是'\n'
if('\n' == usr[len-1]) {
    //  证明输入不足size-1
    usr[len-1] = '\0';
} else {
    //  证明输入超过size-1个,\n在缓冲区中,有残留
    //  清理输入缓冲区
    //while('\n' != getch());
    stdin->_IO_read_ptr = stdin->_IO_read_end;
}
操作字符串的常用函数:
size_t strlen(const char *s);
功能:计算字符串的长度,不包括'\0'
        
char *strcpy(char *dest, const char *src);
功能:把字符串src拷贝到dest处,相当于 = 运算符
注意:会把src末尾的'\0'一起拷贝过来

char *strcat(char *dest, const char *src);
功能:把src字符串追加到dest的末尾  相当于 += 运算符
注意:从dest的\0开始追加src,并且会把src的\0一起追加过来

int strcmp(const char *s1, const char *s2);
功能:按字典序比较两个字符串
    s1 > s2 返回正数
    s1 < s2 返回负数
    s1 == s2 返回0
    逐个字符进行比较,一旦出结果立即结束,后面的不再比较

注意strlensizeof的区别

字符串相关函数:
int atoi(const char *nptr);
功能:字符串转int类型

long atol(const char *nptr);
功能:字符串转long类型

long long atoll(const char *nptr);
功能:字符串转long long类型

double atof(const char *nptr);"2.4"
功能:字符串转double类型

char *strstr(const char *haystack, const char *needle);
功能:查找haystack中是否存在needle
返回值:needle第一次在haystack出现的位置,如果找不到返回NULL
"abcdefcd"  "cad"

char *strchr(const char *s, int c);
功能:查找字符串s中是否有字符c。
返回值:c在s中第一次出现的位置,如果找不到返回NULLint sprintf(char *str, const char *format, ...);
功能:把任意类型的数据输出到str中 把任意类型的数据拼接成字符串
返回值:字符串str的长度

int sscanf(const char *str, const char *format, ...);
功能:从str中读取任意类型数据 从字符串中解析任意类型的数据
返回值:成功读取到的变量个数    

上一篇:指针

下一篇:结构

posted @   sleeeeeping  阅读(97)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
  1. 1 吹梦到西洲 恋恋故人难,黄诗扶,妖扬
  2. 2 敢归云间宿 三无Marblue
敢归云间宿 - 三无Marblue
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

敢归云间宿 - 三无Marblue

词:怀袖

曲:KBShinya

编曲:向往

策划:杨颜

监制:似雨若离RainJaded/杨颜

吉他:大牛

混音:三无Marblue

和声:雾敛

母带:张锦亮

映像:似雨若离RainJaded

美术:阿尼鸭Any-a/乙配/雨谷/今风/米可/Eluan

题字:长安

酒 泼去群山眉头

酒 泼去群山眉头

月 悬在人世沧流

空杯如行舟 浪荡醉梦里走

心 生自混沌尽头

心 生自混沌尽头

对 天地自斟自酬

诗随我 遍历春秋

行流水 走笔形生意动

见珠玉 淙淙落纸成诵

拾得浮名有几声 云深处 却空空

耳畔丝竹 清商如雾

谈笑间 却是心兵穷途

飞觞醉月无归宿 便是孤独

不如就化身为风

卷狂沙 侵天幕

吹醒那 泉下望乡 的战骨

昨日边关犹灯火

眼前血海翻覆

千万人跌落青史 隔世号呼

于是沸血重剑共赴

斩以雷霆之怒

肩背相抵破阵开路

万古同歌哭

纵他春风不度 悲欢蚀骨

此去宁作吾

挣过命途 才敢写荣枯

望 云际群龙回首

望 云际群龙回首

任 飘蓬争逐身后

叹冥顽之俦 好景尽付恩仇

收 江声随酒入喉

收 江声随酒入喉

来 提笔御风同游

不觉已 换了春秋

真亦假 泼墨腾烟沉陆

有还无 蝶影纷堕幻目

我与天地周旋久

写尽梦 便成梦

夜雨浇熄 往事残烛

生死间 谁尽兴谁辜负

管他醒来归何处 心生万物

也曾对电光火雨

抛酒樽 镇天枢

护住了 人间多少 朝与暮

烧尽了阴云冥府

烧尽了阴云冥府

且看星斗尽出

浩荡荡尘埃野马 忘怀命数

于是信步鸿蒙之轻

也领苍生之重

与诗与剑颠倒与共

沉眠斜阳中

纵他世事汹涌 万类争渡

此去宁作吾

醉得糊涂 才梦得清楚

潮水 带着叹息轻抚

潮水 带着叹息轻抚

像光阴 漫过大地上幽微草木

有情世 见众生明灭往复

天生自在 何必回顾

晦暗中双掌一拊

立此身 照前路

与某个 阔别的我 决胜负

渺渺兮身外无物

无喜无悲无怖

不过是大梦一场 各自沉浮

于是纵横万相穷通

也守心底灵通

合眼识得星沉地动

也岿然不动

敢令岁月乌有 逍遥长驻

敢归云间宿

遥祝远行人 有道不孤

点击右上角即可分享
微信分享提示