标签 linux 下的文章

linux c内存分配方式以及动态内存分配

从静态存储区分配(由系统控制)

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,staic变量

在栈上分配(由系统控制)

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。效率很高,凡事分配的内存容量有限

在堆上分配,亦称动态内存分配(由自己控制)

程序在运行时用malloc等函数申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生命周期有我们自己决定,使用非常灵活,但问题也最多

malloc函数

void *malloc(size_t size);

  • 使用:需要包含malloc.h头文件
  • 功能:允许从空闲内存池中分配连续内存但不初始化
  • 参数:size参数是一个所需字节
  • 返回:若分配成功则返回一个指向该内存块的指针,在使用时可根据需要做强制类型转换,否则返回NULL指针
calloc函数

void *calloc(size_t num_elements,size_t element_size);

  • 使用:需要包含malloc.h头文件
  • 功能:功能与malloc函数相同,但作初始化
  • 参数:num_elements是所需元素的数量,element_size是每个元素的字节
  • 返回:与malloc相同
realloc含糊

void *realloc(void *ptr,size_t new_size)

  • 使用:需要包含malloc.h头文件
  • 功能:在指针ptr指向的内存基础上扩大或缩小内存
  • 参数:ptr是指向先前通过malloccallocrealloc函数后分配的内存块的指针,new_size是内存块的新尺寸,可能大于或小于原有内存尺寸
  • 返回:若能调整内存大小则返回指向调整后内存的指针,否则返回NULL
realloc函数的使用注意点
  • 当扩展内存时,不会对添加的内存块的字节进行初始化
  • 弱不能调整内存则返回NULL,但原有内存中的数据不会发生改变
  • 若第一个参数为NULL那么功能等同于malloc函数,若第二个参数为0,那么会释放调用内存块。
  • 在缩小或扩大内存时一般不会对其进行移动,若无法扩大内存块(内存块后面的字节已经用于其他木的),那么可能会在别处分配心的内存块,然后把旧块的数据复制到新块中,并将旧块删除释放内存
free函数

void free(void *ptr);

  • 功能:释放由指针ptr指向的动态内存分配的内存块
free函数注意点
  • 如果ptrNULL指针,那么freeptr无论操作多少次都不会出问题
  • 如果ptr不是NULL指针,那么freeptr连续操作两次就会导致程序停止运行

示例

// stdlib会包含malloc
#include <stdlib.h>
#include <stdio.h>
void print_arr(int *p,int length);
int main(void)
{
        size_t length = 0;
        scanf("%d",&length);
        if(length > 0)
        {
                // 使用malloc分配内存
                //int *p = (int*)malloc(length);
                // 使用calloc分配内存
                int *p = (int*)calloc(sizeof(int),length);
                p[0] = 8;
                print_arr(p,length);
                // 使用 realloc修改申请的内存空间的大小
                p = realloc(p,length + 2);
                print_arr(p,length + 2);
                free(p);
                // 注意虽然释放了申请的内存,但是这个注意要清空下指针
                p = NULL;
        }
        return 0;
}
void print_arr(int *p,int length)
{
        for(int i = 0;i < length;i++)
        {
                printf("p[%d] = %d;\n",i,p[i]);
        }
}

gdb高级命令

工作环境相关命令

命令格式含义
set args 运行时的参数指定运行时的参数,如 set args 2
show args查看设置好的运行参数
path dir设定程序的运行路径
show paths查看程序的运行路径
set environment var[=value]设置环境变量
set var=value修改运行时变量的值
show environment [var]查看环境变量
cd dir进入到dir目录,相当于shell中的cd命令
pwd显示当前工作目录
shell command运行shell的command命令

设置断点和恢复命令

命令格式含义
info b查看所设置的断点
break(b) [行号|函数名] <条件表达式>设置断点
tbreak(b) [行号|函数名] <条件表达式>设置临时断点,到达后自动删除
delete [断点号]删除指定断点
disable [断点号]停止指定的断点名,使用info b仍然能查看此断点。同delete一样,省略断点号则停止所有断点
enable [断点号]激活指定断点,省略则激活所有断点号
condition [断点号] <条件表达式>修改对应断点的条件
ignore [断点号] <num>在程序执行时,忽略对应断点n次
step(s)单步恢复程序运行,且进入函数调用
next(n)单步恢复程序运行,但不会进入函数调用
until(u) 行号跳到指定行
finish运行程序,直到当前函数完成返回
continue(c)继续执行函数,直到函数结束或遇到新的断点

查看源码相关命令

命令格式含义
list(l) <行号>|><函数名>查看指定位置代码
file [文件名]加载指定文件
forward-search 正则表达式源代码前后搜索
reverse-search 正则表达式源代码向前搜索
show directories显示定义了的源文件搜索路径
info file显示加载到gdb内存中的代码
disassemble 函数名查看指定函数的反汇编代码

查看运行数据相关命令

命令格式含义
print(p) 表达式|变量查看程序运行时对应表达式和变量的值
x /<n/f/u> <addr>查看内存变量内容。其中n为整数表示显示内存的长度,f表示显示的格式(如:d表示十进制,x表示16进制,o表示八进制,t表示二进制),u表示从当前的地址往后请求显示的字节数,<addr>表示变量的内存的地址
display 表达式设定在单步运行或其他情况中,自动显示的对应表达式的内容(如:display /i $pc 显示c代码和汇编代码)

操作实例

添加删除断点
(gdb) b 5 # 在第五行加个断点
Breakpoint 1 at 0x400483: file src/gdb_test.c, line 5.
(gdb) info b # 查看所有的断点的信息
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400483 in main at src/gdb_test.c:5
(gdb) delete 1 # 根据断点的编号删除断点
(gdb) info b # 再次查看,可以发现断点已经没了
No breakpoints or watchpoints.
打印变量
(gdb) p a
$1 = 1
(gdb) p &a
$2 = (int *) 0x7fffffffe454
(gdb) x /d 0x7fffffffe454 # 十进制方式输出
0x7fffffffe454:    1
(gdb) x /x 0x7fffffffe454 # 十六进制输出
0x7fffffffe454:    0x00000001
(gdb) x /o 0x7fffffffe454 # 八进制输出
0x7fffffffe454:    01
(gdb) x /t 0x7fffffffe454 # 二进制输出
0x7fffffffe454:    00000000000000000000000000000001

curl检测Web 站点的响应时间

命令:

curl -o /dev/null -s -w %{time_connect}:%{time_starttransfer}:%{time_total} http://jinblog.com
2.554:2.984:3.040

输出通常是 HTML 代码。

参数:

-o 参数发送到 /dev/null。
-s 参数去掉所有状态信息。
-w 参数让 curl 写出表 1 列出的计时器的状态信息:
计时器 描述
time_connect 建立到服务器的 TCP 连接所用的时间
time_starttransfer 在发出请求之后,Web 服务器返回数据的第一个字节所用的时间
time_total 完成请求所用的时间

以上计时器都相对于事务的起始时间,甚至要先于 Domain Name Service(DNS)查询。

因此,在发出请求之后,Web 服务器处理请求并开始发回数据所用的时间是 0.272 - 0.081 = 0.191 秒。

客户机从服务器下载数据所用的时间是 0.779 - 0.272 = 0.507 秒。

通过观察 curl 数据及其随时间变化的趋势,可以很好地了解站点对用户的响应性。

转载自 http://www.jbxue.com/LINUXjishu/13381.html

objdump命令的使用

objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它还有其他作用,下面以ELF格式可执行文件test为例详细介绍:

objdump -f test
显示test的文件头信息

objdump -d test
反汇编test中的需要执行指令的那些section

objdump -D test
与-d类似,但反汇编test中的所有section

objdump -h test
显示test的Section Header信息

objdump -x test
显示test的全部Header信息

objdump -s test
除了显示test的全部Header信息,还显示他们对应的十六进制文件代码

举例:

将C源代码和反汇编出来的指令对照:

  1. 编译成目标文件(要加-g选项)
    gcc -g -o test.c
  2. 输出C源代码和反汇编出来的指令对照的格式
    objdump -S test.o

如下:

00000000004004c4 <main>:
#include <stdio.h>
#define ARR_LENGTH 3
int main(void)
{
  4004c4:    55                       push   %rbp
  4004c5:    48 89 e5                 mov    %rsp,%rbp
  4004c8:    48 83 ec 30              sub    $0x30,%rsp
    int a=1,b=2,c=3,*p[ARR_LENGTH] = {&a,&b,&c};
  4004cc:    c7 45 f8 01 00 00 00     movl   $0x1,-0x8(%rbp)
  4004d3:    c7 45 f4 02 00 00 00     movl   $0x2,-0xc(%rbp)
  4004da:    c7 45 f0 03 00 00 00     movl   $0x3,-0x10(%rbp)
  4004e1:    48 8d 45 f8              lea    -0x8(%rbp),%rax
  4004e5:    48 89 45 d0              mov    %rax,-0x30(%rbp)
  4004e9:    48 8d 45 f4              lea    -0xc(%rbp),%rax
  4004ed:    48 89 45 d8              mov    %rax,-0x28(%rbp)
  4004f1:    48 8d 45 f0              lea    -0x10(%rbp),%rax
  4004f5:    48 89 45 e0              mov    %rax,-0x20(%rbp)
    for(int i = 0;i < ARR_LENGTH;i++)
  4004f9:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%rbp)
  400500:    eb 27                    jmp    400529 <main+0x65>
    {
        printf("*p[%d] = %d\n",i,*p[i]);
  400502:    8b 45 fc                 mov    -0x4(%rbp),%eax
  400505:    48 98                    cltq   
  400507:    48 8b 44 c5 d0           mov    -0x30(%rbp,%rax,8),%rax
  40050c:    8b 10                    mov    (%rax),%edx
  40050e:    b8 38 06 40 00           mov    $0x400638,%eax
  400513:    8b 4d fc                 mov    -0x4(%rbp),%ecx
  400516:    89 ce                    mov    %ecx,%esi
  400518:    48 89 c7                 mov    %rax,%rdi
  40051b:    b8 00 00 00 00           mov    $0x0,%eax
  400520:    e8 93 fe ff ff           callq  4003b8 <printf@plt>

全面解析C语言中可变参数列表

在上一篇blogLinux内核源码分析--文件系统(三、buffer.c)中最后第二个函数struct buffer_head * breada(int dev,int first, ...) ,里面涉及到可变参数列表,所以就干脆来总结下可变参数列表问题。

大众版

首先来看下怎么使用,然后再来总结下其中一些问题:

 #include<stdio.h>
 #include<stdarg.h>
 
 int test(int num, ...)
 {
     int i, result = 0;
 
     va_list ap;//这里写的什么list,(搞得好像是得到可变参数列表头一样)其实它就是个字符指针:char * 
     va_start(ap, num);// 这里把上面得到的字符指针,后移动4个字节,就是跳过num的内存地址
     printf("num:%d, *ap:%d\n", num, *ap);// 这里打印下就会看出,*ap 跳过了num指向了下一个参数
     
     for (i = 0; i < num; i++)//这里num表示可变参数列表中有多少个参数(num本身算不算,由自己觉得,这里是不算入参数个数的)
     {   
         result = va_arg(ap, int);//这里把ap往后跳过4个字节(sizeof(int)大小)指向下一个参数,返回的是当前参数(而非下一个参数)
         printf("in for  result:%d,  *ap:%d\n", result, *ap);//这里打印下,可以看出,ap总是指向result后面的那个参数
     }   
     va_end(ap);//结束标志
 
     return result;
 }
 //下面是测试函数
 int main()
 {
     int i = 4, j = 1, k = 2, g = 3, z = 4, m = 10; 
     printf("result:%d\n", test(i, j, k, g, z, m));
     return 0;
 }

看完后估计大家都会觉得很简单,这是大众版的,就是可变参数列表中第一个参数用来表示可变参数列表有多少个(至于算不算上他自己,那就看你程序自己设计了);

真实版

下面来看下真实版的程序:


 #include<stdio.h>
 #include<stdarg.h>
 
 int test(int first, ...){
 
     va_list args;
     va_start(args, first);
     printf("args:%d  first:%d\n", *args, first);
 
     while( (first = va_arg(args, int)) >= 0 ){
         printf("*args:%d  first:%d\n", *args, first);
     }   
     va_end(args);
     return 0;
 }
 int main()
 {
     int a = 100, i = 1, j = 2, k = 3, g = -1; 
 
     printf("test1:\n");
     test(a, i, j, k, g); 
 
     printf("test2:\n");
     a = 200, i = 11, j = 12, k = 13; 
     test(a, i, j, g); 
 
     return 0;
 }

这个和上面是一样的,唯一不同的是可变参数列表的第一个参数,没有用来当作参数个数,而是把最后一个参数用负数作为结束标志,可变参数列表第一个参数在这里的作用仅仅是为了得到可变参数列表的起始地址;(貌似这个程序上面的那个有点冗余,但是对照这个程序兴许会更好理解可变参数列表的应用)

实际原理

可变参数列表的实现是由几个宏组成的,在文件include/stdarg.h(我看的源码是0.11版本的,但是上面编译是在2.6版本内核上的,根据编译运行得到的结果,可以推理出可变参数列表实现程序在这两个版本中是一样的,也就是说两个版本内核中可变参数列表代码是相同的)中:
va_list 定义某个变量,其本质就是:

typedef char *va_list;//字符指针类型

va_start(ap, type)开始获取可变参数列表中的第一个参数(...里面的第一个),也就是跳过第一个参数(这里的第一个是num或first)

#ifndef __sparc__
#define va_start(AP, LASTARG)                         \
 (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))//ap指向下一个参数,lastarg不变
#else
#define va_start(AP, LASTARG)                         \
 (__builtin_saveregs (),                        \
  AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG))) //跳过下第一个参数,指向第二个参数内存地址
#endif

//对type向上取整 取int的整 4,然后乘上int整型4的倍数
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

va_arg(args, int)循环获取到可变参数列表中的参数,args指向下一个参数地址,返回的则是当前参数地址

//  first=va_arg(args,int)
#define va_arg(AP, TYPE)                        \//ap指向下一个类型的参数
 (AP += __va_rounded_size (TYPE),                    \//返回ap - sizeof(type)参数,即前一个参数
  *((TYPE *) (AP - __va_rounded_size (TYPE))))

//对type向上取整 取int的整 4,然后乘上int整型4的倍数
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

最后一个va_end(ap)结束标志(没有好像也没关系),可能只是在程序中作为一个可变参数列表的结束标志而已(stdarg.h里面只是仅仅定义了下,没有实现的代码部分)。从上面的分析就可以知道第二个程序(所谓的真实版)是怎么来的了,其实根据实现原理来讲第一个参数是没有规定表示可变参数个数的,而是用来得到可变参数列表的起始地址。

自己实现可变参数列表

如果看懂了上面的可变参数列表原理,那么自己动手写个可变参数列表程序解析下多个参数问题,其实很简单的:

#include<stdio.h>//这里不需要用到可变参数列表解析宏,所以不用包含stdarg.h头文件
 
 int test(int first, ...)
 {
     int count = 0;
 
     char *ap ;
     ap = (char*)(&first);//得到参数列表的起始地址
 
     ap = ap + 4;//加上4跳过第一个参数first
     while(count++ < first){//这里first参数表示有多少个参数
         printf("*ap:%d\n", *((int *)ap));//把参数列表中的参数都挨个打印出来
         ap += 4;//指向下一个参数
     }   
     return *ap;
 }
 //下面是测试程序
 int main(void)
 {
     int i = 1, j = 2, k = 3, z = 4, num = 4;
     printf("test1:\n");
     test(num, i, j, k, z); 
 
     num = 3;
     printf("\ntest2:\n");
     test(num, i, j, k); 
     return 0;
 
 }

可变参数列表缺陷

不知道大家有没有发现,上面的所有可变参数都是int型的,就算你用字符作为参数(char  c = 'a'),在可变参数里面(三个小点里面)也同样会为他分配4个字节地址空间;如果是浮点型那么就会报错,至于具体为什么报错我还没有去看那三个点的实现(就是可变参数列表内存分配问题,我估计在库文件里面,后期看到再分析下);为什么不能用其他类型(非整型)的参数呢?我估计是开始设计的时候,为了简便,统一用整型(这样更好实现嘛),可是现实中这样就存在一个缺陷问题,那怎么解决让可变参数列表中可以使用浮点型、字符串呢?
答案是我也不知道,我只是想到了个方案,用第一个参数来表示可变参数列表中的类型,当然不能用int型了,而是用字符数组来表示,这也有个限制那就是只能有一个字符串而且是放在最后(当然要传多个字符串也可以在数组中自己设置),因为字符串长度不知道,指针不知道偏移多少位,所以把它放在最后就好了。
#include<stdio.h>
 
 int test(char num[], int i, char c, float f, char *s)
 {
     int ti;
     char tc;
     float tf;
     char *ts;
 
     printf("int:%p, char:%p, float:%p, char:%p\n", &i, &c, &f, &s);
 
     int count = 0;
 
     char *ap ;
 
     ap =  num + sizeof(num);
 
     while(num[count] != '\0'){
         switch(num[count++]){
 
             case 'i':
                 ti = *((int *)ap);
                 ap += sizeof(int);
                 printf("ti:%d\n", ti);
                 break;
             case 'c':
                 tc = *ap++;
                 printf("tc:%c\n", tc);
                 break;
 
             case 'f':
                 tf = *((float*)ap);
                 ap += sizeof(float);
                 printf("tf:%f\n", tf);
                 break;

             case '1':
                 ts = ap;
                 printf("ts:%s\n", ts);
                 break;
 
             default:
                 printf("No the type!\n");
         }
     }
 
     return 0;
 }
 
 int main(void)
 {
     int i = 12;
     char c = 'a';
     float f = 5.20;
     char *s = "yuzhihui";
 
     char num[4] = {'\0'};
      num[0] = 'i';
      num[1] = 'c';
      num[2] = 'f';
      num[3] = '1';
 
     test(num, i, c, f, s);
 
     return 0;
 
 }

上面就是我设计的一个雏形,大概意思就是这样,但运行时得不到正确结果,原因很有可能是可变参数列表中出问题了。因为...可变参数列表其在内存中的地址是连续的,而我上面的却不一定是连续的(模拟的可变参数列表),还有字节之间对齐问题(因为整型刚好是4个字节对齐的不存在这个问题).但大概意思就是这样的。
这个缺陷的改变可能并没有太多实际意义,我也是突发奇想,高手轻喷。哈哈
如果有什么不正确之处,欢迎大家指正,一起努力,共同学习!!
原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/43734663