标签 c 下的文章

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

bzero, memset ,setmem 区别

bzero

原型:
extern void bzero(void *s, int n);

用法:
#include <string.h>

功能:置字节字符串s的前n个字节为零。
说明:bzero无返回值。
举例:

// bzero.c            
#include <syslib.h>
#include <string.h>
int main()
{ 
        struct
        {
              int a;
              char s[5];
              float f;
        } tt;
        char s[20];
        bzero(&tt,sizeof(tt));   // struct initialization to zero bzero(s,20);
        clrscr();
        printf("Initail Success");
        getchar();
        return 0;
}

memset

原型:
extern void *memset(void *buffer, int c, int count);

用法:
#include <string.h>
功能:把buffer所指内存区域的前count个字节设置成字符c。
说明:返回指向buffer的指针。
举例:

// memset.c
#include <syslib.h>
#include <string.h>
int main()
{ 
   char *s="Golden Global View";
    clrscr();
   memset(s,'G',6);
   printf("%s",s);
   getchar();
    return 0;
}

setmem

原型:
extern void setmem(void *buf, unsigned int count, char ch);

用法:
#include <string.h>

功能:把buf所指内存区域前count个字节设置成字符ch。
说明:返回指向buf的指针。
举例:

// setmem.c
#include <syslib.h>
#include <string.h>
int main()
{ 
    char *s="Golden Global View";
    clrscr();
    setmem(s,6,'G');
    printf("%s",s);
    getchar();
    return 0;
}

综述:

bcopy和memcpy、bzero和memset、bcmp和memcmp的差别在哪里?
bcopy、bzero和bcmp是传统BSD的函数,属于POSIX标准;mem*是C90(以及C99)标准的C函数。区别在于,如果你打算把程序弄到一个符合C90/C99,但是不符合POSIX标准的平台时,后者比较有优势。

NetBSD的代码中有很多地方使用mem(他们更偏爱mem,以利于移植),即使内核也是如此,而FreeBSD的内核中则尽量避免使用(希望尽可能避免在内核中出现较多的C函数)。如果你提交代码的话需要注意这些约定。

在memset和bzero初始化数据间,我很多时候选择bzero, memset的一个缺点是第二个参数和第三个参数需要记忆,需要记住哪个是值和哪个是大小(如果不想查手册的话), 不可以弄错。

bzero()和memset()
今天用到一个字符数组初始化函数,bzero(),因为比较生疏,于是在查本地的一个C/C++函数库的时候并未见此函数,于是便觉得自己拥有的CHM版的库函数软件包有点山寨了,可是当自己调试程序的时候却发现此函数始终通不过编译。被逼上网查它们的区别,得答案如下:

C has memset(), the Berkeley UNIX C library has bzero(). They are not
identical, and bzero() pre dates memset() but is not widely available (since it's not part of standard C).


From CSDN:

【问】网上查了是
#include <string.h>
但是在vc6.0 和vs2005下还是报错,说bzero没有定义
【答】确实没有
可以自己定义一个
C/C++ code
#define bzero(a, b) memset(a, 0, b)
bzero()是在linux平台下用的,可以用memset()函数代替,这样就跨平台了。哈哈...
实在要用就像楼上说的那样。

转载自 http://blog.csdn.net/joeblackzqq/article/details/8257877

gcc以及gdb的基本使用

gcc语法:gcc [options] [filenames]
1、基本选项
1)-c:只是编译不链接,生成目标文件”.o”
2)-S:只是编译不汇编,生成汇编代码
3)-E:只进行预编译,不做其它处理
4)-g:在可执行程序中包含标准调试信息
5)-o file:指定输出文件
6)-v :打印出编译器内部编译各过程的命令行信息和编译器的版本
7)-std=name:指定C语言的标准(如:c99等)
8)-I dir :在头文件的搜索路径列表中添加dir目录
2、警告和出错选项
1)-ansi:支持符合ANSI标准的C程序
2)-pedantic:允许发出ANSI C标准所列的全部警告信息
3)-pedantic-error:允许发出ANSI C标准所列的全部错误信息
4)-w:关闭所有警告
5)-Wall:允许发出gcc所提供的所有有用的报警信息
3、优化
-O:减小代码的长度和执行时间,效果等价于O1,其中包括线程跳转和延迟退栈
-O2:除完成所有O1级别的优化之外,同时进行一些额外的调整工作,如处理器指令调度
-O3:除完成所有O2级别优化之外,还包括循环展开和其他一些与处理器特性相关的优化工作
数字越大优化的等级越高,也就意味着程序的运行越快,一般采用O2选项,他在优化长度,编译时间和代码大小之间取得了一个比较理想的平衡点
4、制作库文件
-L dir 在库文件搜索列表中添加dir目录
-static 链接静态库
-lname 链接名为name的库文件
-shared 表明是用共享库

一、gdb简介
1、GDB是GNU开源组织发布的一个强大的Unix/Linux下的程序调试工具
二、gdb作用
1、启动用户程序后,可以按照用户的要求随意运行程序。
2、可让被调试的程序在用户所设定的断点处停住
3、当程序被停住时,可以检查此时用户程序中所发生的事。
4、可动态改变用户程序的执行环境
三、gdb语法
1、gcc -g [其它选项] [文件名]
2、gdb 可执行文件‘
3、gdb进行调试的是可执行文件而不是源代码
4、对.c源文件进行编译一定要加上选项”-g”,这样编译出的可执行文件才包含调试信息。
四、gdb调试命令
1、l(list):查看所载入的文件
2、b(break):设置断点,程序运行到断点即可停止。
3、nfo b:查看设置的断点情况
4、r(run):从第一行开始运行代码,或者指定行开始,可在r后面加上行号。
5、p n:查看变量n的值
6、n(next):单步运行下一行代码(遇到函数不会进入函数)
7、s(step):单步运行下一行代码(遇到函数会进入函数)
8、c(continue):恢复程序的运行,执行剩余的程序。

函数指针数组

#include <stdio.h>
int addition(int i,int j)
{       
        return i + j;
}
int substraction(int i,int j)
{       
        return i - j;
}
int multiplication(int i,int j)
{       
        return i * j;
}
int division(int i,int j)
{       
        return i / j;
}
void menu(void)
{       
        printf("**************************\n");
        printf("**  0 => addition       **\n");
        printf("**  1 => substraction   **\n");
        printf("**  2 => multiplication **\n");
        printf("**  3 => division       **\n");
        printf("**  4 => quit           **\n");
}
int main(void)
{
        // 定义数组,注意这个格式
        int (*actions[])(int,int) = {addition,substraction,multiplication,division};
        int action = 0,max_action_index = sizeof(actions) / sizeof(actions[0]) - 1,left,right,result;
        while(1)
        {
                menu();
                printf("Enter your action:");
                scanf("%d",&action);
                // 清空输出缓冲
                setbuf(stdin,NULL);
                if(action == 4)
                {
                        printf("Bye\n");
                        break;
                }
                if(action > 0 || action < max_action_index)
                {
                        printf("Enter your variables:");
                        scanf("%d %d",&left,&right);
                        result = actions[action](left,right);
                        printf("result is %d\n",result);
                }
                else
                {
                        printf("Bad action!\n");
                }
                // 清空输出缓冲
                while((action = getchar()) != '\n' && c != EOF);
                action = max_action_index;
                //setbuf(stdin,NULL);
        }
}