Contents

python输出内容,然后用C将其读出

python脚本: a.py

# coding =utf-8
import os,sys,time,glob,traceback,gc,re,string,stringgrep
import time,socket,random,unittest

reload(sys)
sys.setdefaultencoding("utf-8")
os.system("echo 3900/48412 line\(44.7\) Branch\(43.9%\)>out.txt"

C 代码

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char CoverageOut[128] = {0};
    system("python2.7.exe a.py");
    FILE *pFile = fopen("out.txt", "r");
    if (NULL == pFile)
    {
        printf("failed to open out.txt\n");
        return 0;
    }
    fread(CoverageOut, 1024, 1, pFile);
    printf("Coverage Out is %s\n", CoverageOut);
    if (pFile)
    {
        fclose(pFile);
    }
    return 0;
}

alarm 函数的应用

linux代码段

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static void sig_alarm(int sigNo)
{
    printf("in sig_alarm, paraIn = %d\n", sigNo);
    system("date");   //注意退出时,打印的时间间隔
    return;
}

int main(void)
{
    signal(SIGALARM, sig_alarm); //注册函数,时间到则调用此函数
    system("data"); //程序启动时,先打印一个基准时间
    printf("alarm(4) = %d\n", alarm(4)); //如果没有下面的sleep(1)和alarm(8)两行代码,则过4秒打印并跳过pause()继续执行
    sleep(1);  //这个时间被统计进alarm()延时中,需要加上sleep值
    alarm(8); //如果同时开启alarm(4) 和alarm(8),则以长的时间为准
    pause();//在C语言中,如果没有alarm()语句,则会一直卡在这里
    printf("print before end ...\n"); // 在9秒之后,继续从pause的地方执行
}
在这个场景中,如果所注册的 sig_alarm函数中,调用了exit(0)函数,则程序提前退出。同样如果同时调用多个alarm()函数,则以时间最长的为准。

最能体现代码执行时序的例子:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler() {  printf("hello!\n");
void main()
{
    int i = 0;
    signal(SIGALRM,handler);
    alarm(3);
    for (i = 1; i < 5; i ++)
    { printf("sleep %d ...\n", i); sleep(1);}
}

编译动态链接库,示例项目

a.c:  int a= 1;
b.c:  int b = 1;
c.c : int c = 1;
main.c:
#include <stdio.h>
extern int a;
extern int b;
extern int c;
int main()
{
    printf("a=%d b=%d c=%d\n", a, b, c);
    return 0;
}
第一步:编译动态库
     gcc -shared -fpic a.c b.c c.c
     gcc -shared -fpic -o libprog.so  a.o b.o c.o
   或者将两行合并成一行写
     gcc -shared -fpic a.c b.c c.c -o libprog.so
 第二步,编译可执行文件
    gcc main.c -o main -L./   ./libprog.so  ==>>运行成功
    gcc main.c -o main -L./   libprog.so  ==>>运行失败???
另外,链接关键字 -fpic 换成 -fPIC 同样运行成功,具体原理看过,没完全理解,现在又忘了
其它写法,能编译成功,但是运行失败:
    gcc main.c -o main -L ./ -lprog ==>>多标准的写法,运行不了</stdio.h>

C语言解析CSV的正确打开方式

char *pBuff = NULL;
char fileBuff[4*1024*1024] = {0};
FILE *fp = fopen("./a.csv", "r");
//以字符为单位:1,最多读取4*1024*1024个长度
ret = fread(fileBuff, 1, 4*1024*1024, fp);
pBuff = strtok(fileBuff, "\t");  //将csv copy出来,它是以tab间隔的,所以要以"\t"来分割
pBuff = strtok(fileBuff, ","); //csv原文件是以逗号间隔的,所以要以","来分割
pBuff = strtok(fileBuff, "\r\n"); //csv原文件一般包含多列,最后一列肯定要以换行符来结束
if (pBuff)
{
    strcpy(yourBuff, pBuff);
    printf("yourBuff is : %s\n", yourBuff);
}
else
{  //一般都包含多行,所以需要在一个循环中处理
    break;
}
而且第一个元素需要从总buffer中读取,后续所有的strtok操作,第一个参数都是NULL.

gcc 中的 weak 关键字

1、当没有遇到“强”函数时的效果

aa.c:
#include <stdio.h>
__attribute__((weak)) void fun1(int a)
{
    printf("a=%d\n", a);
}
int main()
{
    fun1(3);
    return 0;
}

此时,执行: gcc aa.c -o a.out ; ./a.out
得到的输出是a=3

2、当遇到“强”函数时,立即失效,被别人代替

bb.c:
#include <stdio.h>
void fun1(int a)
{
    printf("new a=%d\n",a*3);
}

此时执行: gcc aa.c bb.c -o a.out; ./a.out
得到的输出是 new a=9

栈,调试 https://blog.csdn.net/hh012938/article/details/78375458

1、backtrace 
一些内存检测工具如Valgrind,调试工具如GDB,可以查看程序运行时函数调用的堆栈信息,有时候在分析程序时要获得堆栈信息,借助于backtrace是很有帮助的,其原型如下:
#include <execinfo.h>
int backtrace(void **buffer, int size); 
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
头文件“execinfo.h”提供了三个相关的函数,简单的说,backtrace函数用于获取堆栈的地址信息, backtrace_symbols函数把堆栈地址翻译成我们易识别的字符串, backtrace_symbols_fd函数则把字符串堆栈信息输出到文件中


backtrace:该函数用于获取当前线程的函数调用堆栈,获取的信息将存放在buffer中,buffer是一个二级指针,可以当作指针数组来用,数组中的元素类型是void*,即从堆栈中获取的返回地址,每一个堆栈框架stack frame有一个返回地址,参数 size 用来指定buffer中可以保存void* 元素的最大值,函数返回值是buffer中实际获取的void*指针个数,最大不超过参数size的大小。 
backtrace_symbols:该函数把从backtrace函数获取的信息buffer转化为一个字符串数组char**,每个字符串包含了相对于buffer中对应元素的可打印信息,包括函数名、函数的偏移地址和实际的返回地址,size指定了该数组中的元素个数,可以是backtrace函数的返回值,也可以小于这个值。需要注意的是,backtrace_symbols的返回值调用了malloc以分配存储空间,为了防止内存泄露,我们要手动调用free来释放这块内存。 
backtrace_symbols_fd:该函数与backtrace_symbols 函数功能类似,不同的是,这个函数直接把结果输出到文件描述符为fd的文件中,且没有调用malloc。 
在使用以上三个函数时,还需要注意一下几点: 
(1)如果使用的是GCC编译链接的话,建议加上“-rdynamic”参数,这个参数的意思是告诉ELF连接器添加“-export-dynamic”标记,这样所有的符号信息symbols就会添加到动态符号表中,以便查看完整的堆栈信息。 
(2)static函数不会导出符号信息symbols,在backtrace中无效。 
(3)某些编译器的优化选项对获取正确的函数调用堆栈有干扰,内联函数没有堆栈框架,删除框架指针也会导致无法正确解析堆栈内容。
下面是一个简单的例子

//backtrace_ex.cpp
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h> 
void my_backtrace()
{
    void *buffer[100] = { NULL };
    char **trace = NULL;
    int size = backtrace(buffer, 100);
    trace = backtrace_symbols(buffer, size);
    if (NULL == trace) {
        return;
    }
    for (int i = 0; i < size; ++i) {
        printf("%s\n", trace[i]);
    }
    free(trace);
    printf("----------done----------\n");
}
void func2()
{     my_backtrace(); 
} 
void func()
{     func2();
} 
int main()
{     func();     return 0;
}
编译执行上面的文件
g++ backtrace_ex.cpp
./a.out
./a.out() [0x400811]
./a.out() [0x400baf]
./a.out() [0x400bba]
./a.out() [0x400bc5]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f2473cf5ec5]
./a.out() [0x400709]
----------done----------

咦!堆栈信息虽然打出来了,但是函数调用栈并不是很明确,原因是少了“-rdynamic”参数,重新编译执行如下:
g++ -rdynamic backtrace_ex.cpp
./a.out
./a.out(_Z12my_backtracev+0x44) [0x400b11]
./a.out(_Z5func2v+0x9) [0x400eaf]
./a.out(_Z4funcv+0x9) [0x400eba]
./a.out(main+0x9) [0x400ec5]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f006bdfbec5]
./a.out() [0x400a09]
----------done----------
加了“-rdynamic”参数后就很好了,我们可以看到函数名称,由于不同的平台、编译器有不同的编译规则,所以用backtrace解析出来的函数名形式是不同的,以“./a.out(_Z4funcv+0x9) [0x400eba]”为例说明,重点在于圆括号中的内容,“_Z”是个函数名开始标识符,后面的“4”表示函数名长度,接着便是真正的函数名“func”,后面的“v”表示函数参数类型为void,随后的“+0x9”是偏移地址。虽然有一定的编译规则,但可读性还不是很好,我们可以用下面介绍的方法demangle来解析这些符号

2、demangle 
demangle即符号重组,函数原型如下:
#include <cxxabi.h>
char* __cxa_demangle(const char* __mangled_name,
                     char* __output_buffer,
                     size_t* __length,
                     int* __status);

cxxabi.h是一个C++函数运行时库,要用g++编译链接,gcc会有问题。__mangled_name即原符号信息,是个字符串,以空字符结尾,__output_buffer用来保存符号重组后的信息,长度为__length,__status表示demangle结果,为0时表示成功,返回值指向符号重组后的字符串首地址,字符串以空字符结尾。 
我们使用demangle来改进上面的例子:(把my_backtrace替换为my_backtrace2)
void my_backtrace2()
{
    void *buffer[100] = { NULL };
    char **trace = NULL;
    int size = backtrace(buffer, 100);
    trace = backtrace_symbols(buffer, size);
    if (NULL == trace) {
        return;
    }
    size_t name_size = 100;
    char *name = (char*)malloc(name_size);
    for (int i = 0; i < size; ++i) {
        char *begin_name = 0;
        char *begin_offset = 0;
        char *end_offset = 0;
        for (char *p = trace[i]; *p; ++p) { // 利用了符号信息的格式
            if (*p == '(') { // 左括号
                begin_name = p;
            }
            else if (*p == '+' && begin_name) { // 地址偏移符号
                begin_offset = p;
            }
            else if (*p == ')' && begin_offset) { // 右括号
                end_offset = p;
                break;
            }
        }
        if (begin_name && begin_offset && end_offset ) {
            *begin_name++ = '\0';
            *begin_offset++ = '\0';
            *end_offset = '\0';
            int status = -4; // 0 -1 -2 -3
            char *ret = abi::__cxa_demangle(begin_name, name, &name_size, &status);
            if (0 == status) {
                name = ret;
                printf("%s:%s+%s\n", trace[i], name, begin_offset);
            }
            else {
                printf("%s:%s()+%s\n", trace[i], begin_name, begin_offset);
            }
        }
        else {
            printf("%s\n", trace[i]);
        }
    }
    free(name);
    free(trace);
    printf("----------done----------\n");
}
结果如下:
g++ -rdynamic backtrace_ex.cpp
./a.out
./a.out:my_backtrace2()+0x44
./a.out:func2()+0x9
./a.out:func()+0x9
./a.out:main()+0x9
/lib/x86_64-linux-gnu/libc.so.6:__libc_start_main()+0xf5
./a.out() [0x400a09]
----------done----------
可以看出来,demangle后函数名已清晰地显示出来了,没有那些奇奇怪怪的符号了。

C 语言正则表达式

1、先上一段代码

#include <stdio.h>
#include <sys/types.h>
#include <regex.h>
#include <memory.h>
#include <stdlib.h>

int main(){
    char *bematch = "hhhericchd@gmail.com";
    char *pattern = "h{3,10}(.*)@.{5}.(.*)";
    char errbuf[1024];
    char match[100];
    regex_t reg;
    int err,nm = 10;
    regmatch_t pmatch[nm];

    if(regcomp(®,pattern,REG_EXTENDED) < 0){
        regerror(err,®,errbuf,sizeof(errbuf));
        printf("err:%s\n",errbuf);
    }

    err = regexec(®,bematch,nm,pmatch,0);

    if (err == REG_NOMATCH){
        printf("no match\n");
            exit(-1);
    }
    else if(err) {
        regerror(err,®,errbuf,sizeof(errbuf));
        printf("err:%s\n",errbuf);
        exit(-1);
    }

    for(int i=0;i<10 && pmatch[i].rm_so!=-1;i++) {
        int len = pmatch[i].rm_eo-pmatch[i].rm_so;
        if(len) {
            memset(match,'\0',sizeof(match));
            memcpy(match,bematch+pmatch[i].rm_so,len);
            printf("%s\n",match);
        }
    }
    return 0;
}

2、编译运行效果

[Administrator.WINDOWS-LGJ801D] ➤ gcc regExp.c -o ppc
[Administrator.WINDOWS-LGJ801D] ➤ ./ppc
hhhericchd@gmail.com
ericchd
com

3、代码理解及验证

A、重复字符匹配

char *pattern = "h{3,10}(.*)@.{5}.(.*)";
显然是核心,这是一个正则表达式语法.
h{3,10} ==>>表示至少有三个'h'重复,最多10个,以此为开始标记,第11个'h'将被看作普通字符

将 char *bematch = "hhhericchd@gmail.com"; 改为两个'h'开头:"hhericchd@gmail.com"
重新编译运行:
[Administrator.WINDOWS-LGJ801D] ➤ gcc regExp.c -o ppc && ./ppc.exe
no match


再改为5个'h'开头"hhhhhericchd@gmail.com":
[Administrator.WINDOWS-LGJ801D] ➤ gcc regExp.c -o ppc && ./ppc.exe
hhhhhericchd@gmail.com
ericchd
com

再改为10个以上连续'h'开头:"hhhhhhhhhhhhhhhhericchd@gmail.com"
[Administrator.WINDOWS-LGJ801D] ➤ gcc regExp.c -o ppc && ./ppc
hhhhhhhhhhhhhhhhericchd@gmail.com
hhhhhhericchd
com

B、指定个数字符,不作为匹配输出

显然,输出中 ".gmail." 被 ".{5}." 取代

C、输出匹配的内容规则

[0] 表示全匹配字符串
[1] 表示第一个 "(.*)" 匹配成功的内容
[2] 表示第二个 "(.*)" 匹配成功的内容
"尤其注意:
    必须用括号括起来的,才能作为子匹配项。
"

但是有一个神奇的事情,空格的字符串长度竟然为0

D、加戏,增加多场景匹配

一个神奇的事情,空格的字符串长度竟然为0
    char *bematch  = "hhhhhhhhhhhhhhhhericchd@gmail.com";
    char *bematch2 = "hhhhhhhhhhhericchd @gmail.com";
    char *bematch3 = "hhhhhhericchd  @gmail.com";
    char *pattern = "h{3,10}(.*)([ ]*)@.{5}.(.*)";
想要匹配'@'、' @'、'  @'即任意空格和'@'连接的情况,第三个将输出也改一下:
    for(int i=0;i<10 && pmatch[i].rm_so!=-1;i++) {
        int len = pmatch[i].rm_eo-pmatch[i].rm_so;
//        if(len) {
            memset(match,'\0',sizeof(match));
            memcpy(match,bematch+pmatch[i].rm_so,len);
            printf( "match %d: %s\n",i , match);
//        }
    }
则编译运行效果如下:

[Administrator.WINDOWS-LGJ801D] ➤ gcc regExp.c -o ppc && ./ppc
match 0: hhhhhhhhhhhhhhhhericchd@gmail.com
match 1: hhhhhhericchd
match 3: com
match 0: hhhhhhhhhhhhhhhhericchd@gmail
match 1: hhhhhheri
match 3: ail
match 0: hhhhhhhhhhhhhhhhericchd@g
match 1: hhhhhhhhh
match 2:
match 3: d@g

4、在经历了一段时间项目经验后,认识更全面,经验更老道一点

1、排除空格,高风险地方

比如有如下脚本:
  [root]$ ssh 127.0.0.1 -l userName -p 2222
要提取出 `ip / userName / 2222`这三个关键字,可以这样写
char *pattern = "ssh[ ]{1,}(.*)[ ]{1,}-l[ ]{1,}(.*)[ ]{1,}-p[ ]{1,}(.*)[ ]{0,}";
讲解:[ ]{1,} ==>> 表示它后面至少有一个空格,可能有多个
     (.*)    ==>> 是最终
     [ ]{0,} ==>> 最后的一个,是为了清除空格,不被(.*)匹配上
     但是这种写法有缺陷,某些时候,不知道为什么最后的空格没有清除掉,被匹配进最后一个项里面

改进写法:
   char *pattern = "ssh[ ]{1,}([^ ]+)[ ]{1,}-l[ ]{1,}(.*)[ ]{1,}-p[ ]{1,}([^ ]+)";
讲解:用 `([^ ]+)` 代替`(.*)`,可完美避开空格,尤其是最后一项,可以不用考虑末尾的空格影响

带上小括号的,就是将被匹配上的内容,唯一例外就是全字符,就是如果匹配
成功,全字符将被告放入匹配数组 match[0],其它依次放入
match[1]/match[2]

2、开关匹配情况

如:
  [root]$ status on
  [root]$ status off
两种情况都应该被匹配上,正确写法是
char *pattern = "status[ ]{1,}(on|off)[ ]{0,}";
这样 match[0]为全字符,match[1]为 on 或者 off

5、其它人的总结,如下内容不全正确,可以批判接受

字 符 意 义 示 例
* 任意长度的字符串。 a* 表示: 空字符串、aaaa、a…
? 长度为0或者1的字符串。 a? 表示: 空字符串和a。
+ 长度为一个或者多个的字符串。 a+表示:a、aa、aaaaaa…
. 任意字符。 a. 表示:a后跟任意字符。
{} 代表上一规则重复数目
{1,1,s}包含一组匹配花括号,里面有两个数字和一个字符,表示在指定次数范围内找到字符。 
a{3}表示:三个a、
a{1,3}表示:一个到三个a、
a{3,} 表示:大于等于三个a、
{3,7,a}表示在3到7次重复范围内匹配字符a。
[] 集合,代表方括号中任意一个字符。 
[ab] 表示:a或者b都可以
[a-z] 表示:从a到z的字符。
() 组,代表一组字符。 (ab){2}表示:abab。
a/b 同时满足。 a/b表示:字符串a后跟字符串b才能满足要求。
a|b 并列,代表符合a或者符合b都可以,this|that表示: 字符串this或者字符串that都满足要求。
^ 如果放在开头表示代表该规则必须在字符串的开头,其他位置代表字符本身。
  如果放在[]中的开头表示对该集合取反,其他位置代表字符本身。
^a表示:a必须在字符串的开头
[^a]表示:除了a以外的其他字符。
$ 如果放在最后表示该规则必须放在最后,其他位置代表字符本身。 a$表示:a必须在字符串最后。
/:s 正则表达式用 /:s 表示空格。 a/:sb 匹配 a b。
  ==>>根据本人实践,/:s代表空格失败,直接采用[ ]表示空格是成功的
/:a 正则表达式用 /:a 表示字符与数字。 a/:a 匹配 ab、a6 等。
/:c 正则表达式用 /:c 仅表示字符。 a/:c 匹配 ac等,不匹配a1等。
/:p 正则表达式用 /:p 表示可打印字符。
/:D 正则表达式用 /:d 仅表示数字。 a/:c 匹配 a1等,不匹配ac等。
/:x00 正则表达式用 /:x00 表示ASCII字符。
/:r 正则表达式用 /:r 表示回车。
/:N 正则表达式用 /:d 表示换行。

C/C++ 中的单子节对齐问题

有两种方式:
1、pragma
2、__attribute__((packed))
一般为了阅读方便,代码美观,会这样处理:
#define MPACK __attribute__((packed))
typedef struct
{
    char c;
    int i;
}MPACK myStruct;
  这样就实现了单子节对齐

编译静态库和动态库

编译静态库

#正常情况下,就直接将 object 文件链接一起编译生成可执行程序就行了
gcc -c funTest.c
gcc -o testMain testMain.c funTest.o //库文件形式下
gcc -c funTest.c //编译生成funa.o
ar -rsv libfunTest.a funTest.o //ar指令, 编译生成静态库文件
gcc -o testMain testMain.c -L./ -lfunTest //链接静态库文件,生成可执行文件

动态链接库的编译与使用

// 动态库编译生成
gcc -o libtest.so -fPIC -shared funa.c
gcc -o testMain testMain.c ./libtest.so //直接指定动态库位置

C 语言彩色打印

彩色定义

#define NONE "\033[m"
#define RED "\033[0;32;31m"
#define LIGHT_RED "\033[1;31m"
#define GREEN "\033[0;32;32m"
#define LIGHT_GREEN "\033[1;32m"
#define BLUE "\033[0;32;34m"
#define LIGHT_BLUE "\033[1;34m"
#define DARY_GRAY "\033[1;30m"
#define CYAN "\033[0;36m"
#define LIGHT_CYAN "\033[1;36m"
#define PURPLE "\033[0;35m"
#define LIGHT_PURPLE "\033[1;35m"
#define BROWN "\033[0;33m"
#define YELLOW "\033[1;33m"
#define LIGHT_GRAY "\033[0;37m"
#define WHITE "\033[1;37m"

简单应用示例

#include <stdio.h>
#define RED    "\033[0;32;31m"
#define NONE   "\033[m"
#define YELLOW "\033[1;33m"
int main()
{
    printf(RED" the red!\n"NONE);
    printf(YELLOW" the yello!\n"NONE);
    return 0;
}

对 printf 参数进行封装

printf(YELLOW" he is %d years old!\n"NONE, 99);

用法用法:
char buff[512]={0};
sprintf(buff,"this is a print content,num=%d", 99);
colorPrint(YELLOW, buff);

最后封装

typedef enum {
    eRED,
    eLIGHT_RED,
    ...
    eLIGHT_GRAY,
    eWHITE,
}eColor;
int colorPrint(eColor color, const char *format, ...)
{
    char content[512 + 1] = {0};
    va_list argptr;
    memset((char *)&argptr, 0, sizeof(va_list));
    va_start(argptr, format);
    vsnprintf(content, 512, format, argptr);
    va_end(argptr);
    if (eRED == color)
    {
        printf(RED "%s" END, content);
    }
    else if(eLIGHT_RED == color)
    {
        printf(LIGHT_RED "%s" END, content);
    }
    ...
    else if (eWHITE == color)
    {
        printf(WHITE "%s" END, content);
    }
    else
    {
        printf("%s", content);
    }
    return 0;
}
用法:
colorPrint(eRED, "myname:%s,age:%d",name, 99);

怎样输出百分号:‘%’

int main()
{
    printf("%%%s%%\n","xx");
    return 0;
}

C 语言中调用shell语句(linux C)

1、亲自编写的代码

#define ERROR_CODE (1<<8)
void test()
{
    char cmdBuff[512] = {0};
    sprintf(cmdBuff, "grep -rni %d ${PWD%%KEY*}/KEY/file.c || exit 1");
    if (ERROR_CODE ==system(cmdBuf))
    {
        return 1;
    }
    return 0;
}

int ret =  system("exit 1");
则 ret=(1<<8),0x100,256

int ret =  system("exit 2");
则 ret=(2<<8),0x200,512

int ret =  system("exit 3");
则 ret=(1<<8),0x300,768

但是126、127有特殊用途,一般不返回这两个值

2、转链接

https://blog.csdn.net/linluan33/article/details/8097916

编程小技巧:二进制的使用,尤其适合做标准

int parse(int paraIn)
{
  switch(paraIn)
  {
      case 0b0001:
          break;
      case 0b0010:
          break;
      case 0b1011:
          break;
      default:
          break;
  }
    return 0;
}

大小端判断

int isBigEndian()
{
    short i = 1;
    char *p = (char *)&i;
    return (*p == 1) ? __little : __big;
}

构造带参数的函数

int use_func(int argc, char **argv)
{
    return 0;
}

int construct_para_func(int a)
{
    int   argc          = 2;
    char *argv[3]       = {NULL};
    int   argv_0_buf[8] = {0};

    argv[0] = argv_0_buf;
    sprintf(argv_0_buf, "%d", a);

    return use_func(argc, argv);
}

计算文件大小

#include <sys/stat.h>
int file_size2(char* filename)
{
    struct stat statbuf;
    stat(filename,&statbuf);
    int size=statbuf.st_size;
    return size;
}

文件大小裁剪

#include <unistd.h>    //包含ftruncate()头文件
#include <cstdio>       //包含结构体FILE头文件
int main(void)
{
    FILE *in;
    in = fopen("in.txt", "ab+");
    int a=fileno(in);
    ftruncate(a,size);
    return 0;
}

毫秒级打印

#include <sys/time.h>
#include <stdio.h>
int  main(void)
{
    int i;
    struct timeval tv;
    for (i = 0; i < 4; i++)
    {
        gettimeofday(&tv, NULL);
        printf("%d\t%d\n", tv.tv_usec, tv.tv_sec);
        sleep(1);
    }
    return 0;
}

管道 pipe

windows 版管道

linux 版管道

#include <stdio.h>

int main()
{
    FILE *pPipe        = NULL;
    char  oneLine[256] = {0};

    if ((pPipe = (FILE *)popen("ping www.baidu.com -c 3", "r")) == (FILE *)NULL)
    {
        printf("pipe create failed, pipe = %p\n", pPipe);
        return 1;
    }

    while (fgets(oneLine, sizeof(oneLine), pPipe))
    {
        if (strstr(oneLine, "100% packet loss"))
        {
              printf("network bad ...\n");
              break;
        }
        else
        {
              printf("pipe get string :%s\n", oneLine);
        }
    }
    pclose(pPipe);
}
~
运行效果如下:
[wishcell@localhost c]$ ./a.out
pipe get string :PING www.baidu.com (115.239.211.112) 56(84) bytes of data.
pipe get string :64 bytes from 115.239.211.112 (115.239.211.112): icmp_seq=1 ttl=53 time=35.3 ms
pipe get string :64 bytes from 115.239.211.112 (115.239.211.112): icmp_seq=2 ttl=53 time=66.5 ms
pipe get string :64 bytes from 115.239.211.112 (115.239.211.112): icmp_seq=3 ttl=53 time=45.4 ms
pipe get string :
pipe get string :--- www.baidu.com ping statistics ---
pipe get string :3 packets transmitted, 3 received, 0% packet loss, time 2003ms
pipe get string :rtt min/avg/max/mdev = 35.364/49.116/66.508/12.972 ms

C语言长/短参数

ss

目录读取

相关函数:open, opendir, closedir, rewinddir, seekdir, telldir, scandir

头文件:#include <sys/types.h>
       #include <dirent.h>

定义函数:struct dirent * readdir(DIR * dir);

函数说明:readdir()返回参数dir 目录流的下个目录进入点。结构dirent 定义如下:
struct dirent
{
     ino_t d_ino; //d_ino 此目录进入点的inode
     ff_t d_off;  //d_off 目录文件开头至此目录进入点的位移
     signed short int d_reclen; //d_reclen _name 的长度, 不包含NULL 字符
     unsigned char d_type;      //d_type d_name 所指的文件类型 d_name 文件名
     har d_name[256];
 };

返回值:成功则返回下个目录进入点. 有错误发生或读取到目录文件尾则返回NULL.

附加说明:EBADF 参数dir 为无效的目录流。

 范例
 #include <sys/types.h>
 #include <dirent.h>
 #include <unistd.h>
 int main()
 {
     DIR * dir;
     struct dirent * ptr;
     int i;
     dir = opendir("/etc/rc.d");
     while((ptr = readdir(dir)) != NULL)
     {
         printf("d_name : %s\n", ptr->d_name);
     }
     closedir(dir);
 }

linux 下C语言实现ls -l功能(尚未验证)

来自(https://blog.csdn.net/u013007900/article/details/51019433)

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>

void show_file_info(char* filename, struct stat* info_p)
{
    char* uid_to_name(), *ctime(), *gid_to_name(), *filemode();
    void mode_to_letters();
    char modestr[11];

    mode_to_letters(info_p->st_mode, modestr);

    printf("%s", modestr);
    printf(" %4d", (int) info_p->st_nlink);
    printf(" %-8s", uid_to_name(info_p->st_uid));
    printf(" %-8s", gid_to_name(info_p->st_gid));
    printf(" %8ld", (long) info_p->st_size);
    printf(" %.12s", 4 + ctime(&info_p->st_mtime));
    printf(" %s\n", filename);
}

void mode_to_letters(int mode, char str[])
{
    strcpy(str, "----------");

    if (S_ISDIR(mode))
    {
        str[0] = 'd';
    }

    if (S_ISCHR(mode))
    {
        str[0] = 'c';
    }

    if (S_ISBLK(mode))
    {
        str[0] = 'b';
    }

    if ((mode & S_IRUSR))
    {
        str[1] = 'r';
    }

    if ((mode & S_IWUSR))
    {
        str[2] = 'w';
    }

    if ((mode & S_IXUSR))
    {
        str[3] = 'x';
    }

    if ((mode & S_IRGRP))
    {
        str[4] = 'r';
    }

    if ((mode & S_IWGRP))
    {
        str[5] = 'w';
    }

    if ((mode & S_IXGRP))
    {
        str[6] = 'x';
    }

    if ((mode & S_IROTH))
    {
        str[7] = 'r';
    }

    if ((mode & S_IWOTH))
    {
        str[8] = 'w';
    }

    if ((mode & S_IXOTH))
    {
        str[9] = 'x';
    }
}

char* uid_to_name(uid_t uid)
{
    struct passwd* getpwuid(),* pw_ptr;
    static char numstr[10];

    if((pw_ptr = getpwuid(uid)) == NULL)
    {
        sprintf(numstr,"%d",uid);

        return numstr;
    }
    else
    {
        return pw_ptr->pw_name;
    }
}

char* gid_to_name(gid_t gid)
{
    struct group* getgrgid(),* grp_ptr;
    static char numstr[10];

    if(( grp_ptr = getgrgid(gid)) == NULL)
    {
        sprintf(numstr,"%d",gid);
        return numstr;
    }
    else
    {
        return grp_ptr->gr_name;
    }
}
void do_ls(char dirname[])
{
    DIR* dir_ptr;
    struct dirent* direntp;

    if ((dir_ptr = opendir(dirname)) == NULL)
    {
        fprintf(stderr, "ls2: cannot open %s \n", dirname);
    }
    else
    {
        while ((direntp = readdir(dir_ptr)) != NULL)
        {
            dostat(direntp->d_name);
        }

        close(dir_ptr);
    }
}

void dostat(char* filename)
{
    struct stat info;

    if (stat(filename, &info) == -1)
    {
        perror(filename);
    }
    else
    {
        show_file_info(filename, &info);
    }
}

int main(int ac,char* av[])
{
    if(ac == 1)
    {
        do_ls(".");
    }
    else
    {
        while(--ac)
        {
            printf("%s: \n",++*av);
            do_ls(*av);
        }
    }
}

C语言判断字符串是文件还是目录

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

int main()
{
    char* fileName = "aa.txt";
    struct stat buf;
    int result;
    result = stat( fileName, &buf );
    if ( S_IFDIR & buf.st_mode)
    {
        printf("folder\n");
    }
    else if (S_IFREG & buf.st_mode)
    {
        printf("file\n");
    }

    return 0;
}

C语言启动子进程

I、windows下启动子进程

#include <stdio.h>
#include <windows.h>
#include <io.h>
#include <process.h>
#include <direct.h>
#include <stdarg.h>
#include <ctype.h>

void thread_func1()
{
    while (1)
    {
        Sleep(100);
        printf("thread 11111111 print...\n");
    }
}

void thread_func2()
{
    while (1)
    {
        Sleep(100);
        printf("thread 2222222 print...\n");
    }
}

int main(int argc, char **argv)
{
    CRITICAL_SECTION cs_log;
    InitializeCriticalSection(&cs_log);

    system("color 71");
    system("title my_new_prj");
    _beginthread((void(__cdecl *)(void *))thread_func1, 0, (void*)NULL);
    _beginthread((void(__cdecl *)(void *))thread_func2, 0, (void*)NULL);
    while (1)
    {
        Sleep(100);
    }
}

linux启动子进程

A、用fork实现

B、用execv实现,启动多个子进程,新启动的子进程会替换原有image文件

#include <unistd.h>
#include <stdio.h>
int main()
{
    int childpid;
    int i;
    char * execv_str[] = {"echo", "executed by execv",NULL};
    if (fork() == 0){
        if (execv("./a.exe",execv_str) <0 ){
            perror("error on exec");
            exit(0);
        }
    }else{
        wait(&childpid);
        printf("execv done\n\n");
    }

    return 0;
}

C、用clone实现,启动多个子进程

$ cat a.c
#include <stdio.h>
int main()
{
    printf("a.exe prints good\n");
    return 0;
}
$ gcc a.c

15900@DESKTOP-CGPRCF3 ~/aaa
$ ./a.exe
a.exe prints good
#define _GNU_SOURCE
#include "sched.h"
#include "stdio.h"
#include "signal.h"

#define CHILD_STACK 8192
int a;
void * stack;
int childFunc(){
    ++a;
    printf("[Child] a=%d\n", a);
    exit(0);
}
int main() {
    char * stack;
    a = 1;
    //为子进程申请系统堆栈
    stack = (char*) malloc(CHILD_STACK );
    if(!stack) {
        printf("malloc failed/n");
        exit(0);
    }

    printf("create child thread...\n");
    //创建与父进程共享用户空间的子进程,因而,是一个线程
    clone(childFunc, stack + CHILD_STACK , CLONE_VM|CLONE_VFORK, 0);
    printf("[Parent] a=%d\n", a);
    //此处,自动释放stack所指空间

    exit(0);
}

15900@DESKTOP-CGPRCF3 ~/aaa
$ gcc execv_tst.c -o execv_tst.exe
execv_tst.c: 在函数‘main’中:
execv_tst.c:11:13: 警告:隐式声明函数‘exit’ [-Wimplicit-function-declaration]
             exit(0);
             ^~~~
execv_tst.c:11:13: 警告:隐式声明与内建函数‘exit’不兼容
execv_tst.c:11:13: 附注:include ‘<stdlib.h>’ or provide a declaration of ‘exit’
execv_tst.c:14:9: 警告:隐式声明函数‘wait’ [-Wimplicit-function-declaration]
         wait(&childpid);
         ^~~~

15900@DESKTOP-CGPRCF3 ~/aaa
$ ./execv_tst.exe
a.exe prints good
execv done

跨平台socket程序

>代码路径:本 GIT `./project/socket_cross_platform`
>
>```powershell
>手机 git 路径:
>$ git clone ssh://wishcell@192.168.43.153:2222/cSocket.git
>Cloning into 'cSocket'...
>wishcell@192.168.43.153's password:
>Permission denied, please try again.
>wishcell@192.168.43.153's password:
>remote: Counting objects: 6, done
>remote: Finding sources: 100% (6/6)
>remote: Total 6 (delta 0), reused 6 (delta 0)
>Receiving objects: 100% (6/6), 4.89 KiB | 2.45 MiB/s, done.
>```
>

十三、mockcpp在gcc-6.2.0中编译失败

I、提示static_assert定义出错

gcc-4.8.5中编译成功,gcc-6.2.0中编译报错。
解决办法:增加条件判断

#if __plusplus < 199711L
    template <bool condition>
    struct static_assert
    {
        typedef int static_assert_failure[condition ? 1 : -1];
    };
#endif

此文件必须用 root 来编辑

II、提示ABI报错

   解决 static_assert 问题之后。gcc-4.8.5 编译成功, gcc-6.2.0继续报错
    继续报错, mockcpp::toBufferString[abi:cXX11](void *,unsigned long)未定义的引用

    原因,c++ 的 ABI确接口定义问题,解决办法:在 Makefile 中添加宏
 -D_GLIBCXX_USE_CXX11_ABI=0 强制 GCC 使用旧的 ABI 版本    

III、 ABI问题复杂,不是gcc版本一个问题引起

在一台电脑上解决 ABI 的问题。
另一台电脑同样是 gcc-6.2.0 ,增加了-D_GLIBCXX_USE_CXX11_ABI=0 宏。但是仍然报错:
    mockcpp::toBufferString[abi:cXX11](void *,unsigned long)未定义的引用

然后把宏改为:-D_GLIBCXX_USE_CXX11_ABI=1 问题又好了。

于是,在 Makefile中要增加变量,由用户编译时动态输入。

用法:
    make is_old_abi=0  或者  make is_old_abi=1

Makefile 内容大致如下:

DEFS= 
#定义变量 make is_old_abi 默认值为 0,即 make 不带参数是为0
is_old_abi=0 

如果命令没有带参数,或者参数设置的值为0,则采用新的 ABI 规范
ifeq ($(is_old_abi), 0)
    DEFS += -D_GLIBCXX_USE_CXX11_ABI=1
else
    # -D_GLIBCXX_USE_CXX11_ABI=0 强制 GCC 采用旧的 ABI 接口规范
    DEFS += -D_GLIBCXX_USE_CXX11_ABI=0 
endif

十四、获取32位整数第N位的 bit 状态

#define BIT32_N(val,bit) ((((WORD32)val) & (((WORD32)1)<<bit)) ? 1 : 0)

测试代码如下

#include <stdio.h>
#define WORD32 unsigned int
#define BIT32_N(val, bit) ((((WORD32)val) & (((WORD32)1)<<bit)) ? 1 : 0)

int main()
{
    printf("BIT32_N(9,0) = %d\n", BIT32_N(9,0));
    printf("BIT32_N(9,1) = %d\n", BIT32_N(9,1));
    printf("BIT32_N(9,2) = %d\n", BIT32_N(9,2));
    printf("BIT32_N(9,3) = %d\n", BIT32_N(9,3));

    printf("BIT32_N(37,0) = %d\n", BIT32_N(37,0));
    printf("BIT32_N(37,1) = %d\n", BIT32_N(37,1));
    printf("BIT32_N(37,2) = %d\n", BIT32_N(37,2));
    printf("BIT32_N(37,3) = %d\n", BIT32_N(37,3));
    printf("BIT32_N(37,4) = %d\n", BIT32_N(37,4));
    printf("BIT32_N(37,5) = %d\n", BIT32_N(37,5));
    printf("BIT32_N(37,6) = %d\n", BIT32_N(37,6));
    printf("BIT32_N(37,7) = %d\n", BIT32_N(37,7));
    return 0;
}

运行结果:

15900@DESKTOP-CGPRCF3 /f/git_prj/cSocket/project/socket_cross_platform
$ ./aa.exe
BIT32_N(9,0) = 1
BIT32_N(9,1) = 0
BIT32_N(9,2) = 0
BIT32_N(9,3) = 1
BIT32_N(37,0) = 1
BIT32_N(37,1) = 0
BIT32_N(37,2) = 1
BIT32_N(37,3) = 0
BIT32_N(37,4) = 0
BIT32_N(37,5) = 1
BIT32_N(37,6) = 0
BIT32_N(37,7) = 0

15900@DESKTOP-CGPRCF3 /f/git_prj/cSocket/project/socket_cross_platform
$

十五、 动态链接库

1、linux

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>

int main()
{
    void *handle;
    char *error;
    double (*cosine)(double);
#ifdef __x86_64__
    handle = dlopen("/lib64/libm.so.6",RTLD_LAZY);
#else
    handle = dlopen("/lib/libm.so.6",RTLD_LAZY);
#endif
    if (!handle)
    {
        fputs(dlerror(), "cos");
        exit(1);
    }

    cosine = dlsym(handle, "cos");
    if ((error = dlerror()) != NULL)
    {
        fputs(error, stderr);
        exit(1);
    }

    printf("%f\n", (*cosine)(2.0));
    dlclose(handle);
    return 0;
}
# gcc -o dynamic dynamic.c -ldl

如果缺少平台判断,把64位机当成32位机,编译会报错:
 /lib/libm.so.6 : wrong ELF class: ELFCLASS

2、windows`版

A、最基础版本

# makefile 及其用法:

# 编译 dll: gcc -Wall -shared print.c -o print.dll
#     或者:gcc --share print.c -o print.dll

# 主程序调用库:
        gcc test.c print.dll -o test
#include <stdio.h>
#include "print.h"

void Print(int iNum)
{
        switch(iNum)
        {
                case 1:
                        printf("hello,this is 1/n");
                        break;
                case 2:
                        printf("ok ,2 now/n");
                        break;
                default:
                        printf("hihi,default now /n");
                        break;
        }
        getch();
        return;
}
#ifndef PRINT_H
#define PRINT_H

#ifdef __cplusplus
extern " C " {
#endif
        //打印点东西
        void Print(int iNum);
#ifdef __cplusplus
}
#endif
#endif
#include <stdio.h>
#include "print.h"

int main()
{
        printf("please print a num:/n");
        int iNum = -1;
        scanf("%d", &iNum);
        Print(iNum);
        return 1;
}

十五(1)、静态链接库

1、linux 版

2、windows 版

###A、把一个文件制作成静态库,具体代码演示如下: 示例代码 OneFileStatic.zip

用法:
gcc -shared -o print.dll print.c -Wl,--output-def,print.def,--out-implib,libprint.a
H:\h_disk\cleaning\study\C\DllCompile\OneFileStatic>gcc test.c -o asddd -I./ -L./ -lprint
H:\h_disk\cleaning\study\C\DllCompile\OneFileStatic>asddd.exe
hello,this is 1/n请按任意键继续. . .
//print.h
#ifndef PRINT_H
#define PRINT_H
#ifdef __cplusplus
extern " C " {
#endif 
    void Print(int iNum); 
#ifdef __cplusplus
}
#endif 
#endif
//print.c
#include <stdio.h>
#include "print.h"

void Print(int iNum)
{
    switch(iNum)
    {
        case 1:
            printf("hello,this is 1/n");
            break;
        case 2:
            printf("ok ,2 now/n");
            break;
        default:
            printf("hihi,default now /n");
            break;
    }
    getch();
    return;
}
//test.c
#include <stdio.h>
#include <stdlib.h>
#include "print.h"
//#pragma comment(lib,"print.lib")

int main()
{
    Print(1);
    system("pause");

    return 0;
}

###B、把两个文件制作成静态链接库,动态加载 示例代码 TwoFiles.zip

H:\backup_disk\HateIron\code\c\TwoFiles>dir /b
foo.c
foo_test.c
makefile
make_lib.mk
print.c
print.h
readme
用法:
H:\backup_disk\HateIron\code\c\TwoFiles>make -f make_lib.mk     ----编译静态库
MAKE Version 5.4  Copyright (c) 1987, 2009 CodeGear
        gcc -shared -o foo.dll foo.c print.c -Wl,--output-def,print.def,--out-implib,libfoo.a

H:\backup_disk\HateIron\code\c\TwoFiles> make                   ----编译测试文件,同时链接静态库(去掉了告警打印)
MAKE Version 5.4  Copyright (c) 1987, 2009 CodeGear
        gcc foo_test.c -o foo_test.exe -I./ -L./ -lfoo

H:\backup_disk\HateIron\code\c\TwoFiles>foo_test.exe            ----运行目标文件
process attach/nthis is from print.c
this is from print.c
this is from print.c
thread attach/nfrom main, mod=6B080000
from main,1 foo=6B08132F

=========this is called from dll foo() ==============           ---- 发现没有函数,动态加载
hello world

========= dll foo() finish ==============                       ---- 发现没有函数,动态加载
from main,2 foo=6B08132F
thread attach/n
H:\backup_disk\HateIron\code\c\TwoFiles>make clean              ---- 实验完毕,清理中间文件
MAKE Version 5.4  Copyright (c) 1987, 2009 CodeGear
        del *exe *.a *.dll *.def /a/f/s/q
删除文件 - H:\backup_disk\HateIron\code\c\TwoFiles\foo_test.exe
删除文件 - H:\backup_disk\HateIron\code\c\TwoFiles\libfoo.a
删除文件 - H:\backup_disk\HateIron\code\c\TwoFiles\foo.dll
删除文件 - H:\backup_disk\HateIron\code\c\TwoFiles\print.def

H:\backup_disk\HateIron\code\c\TwoFiles>
# make_lib.mk
target:
    gcc -shared -o foo.dll foo.c print.c -Wl,--output-def,print.def,--out-implib,libfoo.a   ---把两个 C 文件封装到一个库中

# makefile
a:
    gcc foo_test.c -o foo_test.exe -I./ -L./ -lfoo

.PHONY:clean
clean:
    del *exe *.a *.dll *.def /a/f/s/q
//print.h
#ifndef PRNT_H
#define PRNT_H
    void my_print();
#endif
//print.c
#include <windows.h>
#include <stdio.h>
#include "print.h"
void my_print()
{
    printf("this is from print.c\n");   
}
//foo.c  这是一个被封装测试的函数
#include <windows.h>
#include <stdio.h>

// 这就是按需加载的dll的主函数,dll被加载、卸载时,系统都回调用这个函数,通过dwReason判断
BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpvReserved)
{
    switch(dwReason) {
        // 如果是进程加载本dll
        case DLL_PROCESS_ATTACH:
            printf("process attach/n");
            break;
        // 如果是进程卸载本dll
        case DLL_PROCESS_DETACH:
            printf("thread attach/n");
            break;
        // 如果是线程加载本dll
        case DLL_THREAD_ATTACH:
            printf("thread attach/n");
            break;
        // 如果是线程卸载本dll
        case DLL_THREAD_DETACH:
            printf("process attach/n");
            break;
    }
    // 如果返回FALSE,则说明加载失败,不会继续被加载,也不可使用
    return TRUE;
}

int foo(char *str)
{
        printf("\n=========this is called from dll foo() ==============\n");
        printf("%s\n", str);
        printf("\n========= dll foo() finish ==============\n");
        return 0;
} 
#include <windows.h>
#include <stdio.h>
#define SUCC 0

typedef int (*FOO)(char *str);

int main()
{
    TCHAR   buff[128];
    HMODULE mod;
    FOO     foo;
    TCHAR*  szErr = buff;

    my_print();
    my_print();
    my_print();
    if (0)
        mod = LoadLibraryEx("foo.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
    else
        mod = LoadLibrary("foo.dll");
    if (SUCC == GetLastError())
    {
//      wsprintf(szErr, "LoadLibrary success!\n");
//      MessageBox(NULL, szErr, "Error", MB_OK|MB_ICONSTOP);
    }
    else
    {
        wsprintf(szErr, "LoadLibrary error code: %d\n", GetLastError());
        MessageBox(NULL, szErr, "Error", MB_OK|MB_ICONSTOP);
    }
    printf("from main, mod=%p\n", mod);
    if((foo = (FOO)GetProcAddress(mod, "foo")) != NULL) {
            printf("from main,1 foo=%p\n", foo);
            foo("hello world");
    }
    printf("from main,2 foo=%p\n", foo);
    FreeLibrary(mod);
    getch();
    return 0;
}

C、对代码目录进行优化,但是优化之后编译出来的版本,运行不正常。优化后的版本

十六、C语言连接符的使用

1、一个很大的作用简化代码

#include <stdio.h>
#define USE_VAR(x) \
     {             \
       printf("this_is_a_%d = %d\n", \
      x, this_is_a_##x);}
int main()
{
    int this_is_a_1 = 11;
    int this_is_a_2 = 11;
    int this_is_a_3 = 12;
    int this_is_a_4 = 13;
    int this_is_a_5 = 14;
    USE_VAR(1);
    USE_VAR(2);
    USE_VAR(3);
    USE_VAR(4);
    USE_VAR(5);
    return 0;
}

输出结果:
[wishcell@localhost concat]$ gcc a.c
[wishcell@localhost concat]$ ./a.out
this_is_a_1 = 11
this_is_a_2 = 11
this_is_a_3 = 12
this_is_a_4 = 13
this_is_a_5 = 14

2、项目中实战用法,快速制表,代码易读

#include <stdio.h>

#define WARNING_A 1000
#define WARNING_B 2000

typedef struct
{
    char *warningType;
    int  code_1;
    int  code_2;
}testStruct;

#define NODE(A) #A,(A+100),(A+200)

int main()
{
    testStruct warningTbl[] = {
            {NODE(WARNING_A)},
            {NODE(WARNING_B)},
            };
    printf("waning_a name:%s, code_1:%d, code_2:%d\n", warningTbl[0].warningType, warningTbl[0].code_1, warningTbl[0].code_2);
    printf("waning_b name:%s, code_1:%d, code_2:%d\n", warningTbl[1].warningType, warningTbl[0].code_1, warningTbl[0].code_2);
    return 0;
}

十七、C函数动态获取调用栈的方法

1、原理

原理其实也简单,主要是利用C编程范围,对函数的参数入栈,组成特定格式的桢。

然后利用相关寄存器,rsprbp读取

2、一份正式的代码,比较理想法

A、理想代码

#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
void print_call_stack()
{
    int    i          = 0;
    int    size       =32;
    void  *array[32];
    int    stack_num  = backtrace(array, size);
    char **stacktrace = (char**)backtrace_symbols(array, stack_num);
    for (i=0; i<stack_num; i++)
    {
        printf("func_%d addr:%s\n", i, stacktrace[i]);
    }
    free(stacktrace);
    return;
}
void func_1(){print_call_stack();}
void func_2(){func_1();}
void func_3(){func_2();}
void func_4(){func_3();}
void func_4(){func_3();}
void func_5(){func_4();}
void func_6(){func_5();}
int main()
{
    func_6();
    return 0;
}

B、编译、运行(带 -rdynamic参数时),==显示了函数名和偏移地址==

[wishcell@localhost c]$ gcc -rdynamic -g stack.c
[wishcell@localhost c]$ ./a.out
func_0 addr:./a.out(print_call_stack+0x2d) [0x4009ea]
func_1 addr:./a.out(func_1+0xe) [0x400a62]
func_2 addr:./a.out(func_2+0xe) [0x400a72]
func_3 addr:./a.out(func_3+0xe) [0x400a82]
func_4 addr:./a.out(func_4+0xe) [0x400a92]
func_5 addr:./a.out(func_5+0xe) [0x400aa2]
func_6 addr:./a.out(func_6+0xe) [0x400ab2]
func_7 addr:./a.out(main+0xe) [0x400ac2]
func_8 addr:/lib64/libc.so.6(__libc_start_main+0xf5) [0x7fec3bf9bc05]
func_9 addr:./a.out() [0x4008f9]
[wishcell@localhost c]$ 

C、编译运行,不带rdynamic参数时,==没有显示函数名==

[wishcell@localhost c]$ gcc stack.c
[wishcell@localhost c]$ ./a.out
func_0 addr:./a.out() [0x40064a]
func_1 addr:./a.out() [0x4006c2]
func_2 addr:./a.out() [0x4006d2]
func_3 addr:./a.out() [0x4006e2]
func_4 addr:./a.out() [0x4006f2]
func_5 addr:./a.out() [0x400702]
func_6 addr:./a.out() [0x400712]
func_7 addr:./a.out() [0x400722]
func_8 addr:/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f4eb5919c05]
func_9 addr:./a.out() [0x400559]
[wishcell@localhost c]$

3、在正式代码中的应用

“`c
需求、需要知道是哪个函数调用的,根据不同的调用者,作出不同的操作

#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
void set_val_stub(int state)
{
int i = 0;
int size =32;
void *array[32];
int stack_num = backtrace(array, size);
char <strong>stacktrace = (char</strong>)backtrace_symbols(array, stack_num);

<pre><code> if (strstr(stacktrace[1], "Func_Name_A"))
{
func_a(state);
}
else if (strstr(stacktrace[1], "Func_Name_B"))
{
func_b(state);
}
free(stacktrace);
return;
</code></pre>

}

“`

十八、atoi函数自己实现

//atoi函数的实现
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<ctype.h>

//参数为要转化的字符串
int myatoi(char arr[])
{
    if(arr == NULL)
    {
        //非法输入
        return -1;
    }
    int index = 0;//记录数组下标
    int num = 0;//存放最终要返回的值
    int flag = 1;//设置符号标志位

    while(isspace(arr[index]))//是空格返回非零值(不一定是1),否则返回0
    {
        index++;//跳过空格字符,不只有空格,还有\n,\r,\f,\t,\v之类的字符
    }
    if(arr[index] == '-')
    {
        flag = -1; //除空格外遇到的第一个数字是符号,标记为-1
    }
    //注意:正负号不算作1
    if(arr[index] == '-' || arr[index] == '+')//如果遇到正负号,说明可以往后继续走
    {
        index++;
    }
    while(arr[index] >= '0' && arr[index] <= '9')//如果遇到的数字说明可以继续往下走
    {
        num = num*10 + arr[index] - '0';
        index++;
    }
    return flag*num;
}
int main()
{
    int a1=atoi("31");
    int b1=atoi("131");
    int c1=atoi("-0993131");
    printf("a1=%d b1=%d c1=%d\n", a1,b1,c1);
    return 0;
}

15900@DESKTOP-CGPRCF3 ~/aaa
$ gcc atoi.c -o atoi

15900@DESKTOP-CGPRCF3 ~/aaa
$ ./atoi.exe
a1=31 b1=131 c1=-993131

十九、调试手段#line的用法

1、基本用法

作用描述:

##### 主要用于更改当前调试行号和文件名 。它改变了当前__LINE____FILE__ 的值,打印时,打印的是更改后的值

#include <stdio.h>
int main()
{
    int a = 5;
#line 10 "line1.c"
    printf("a= %d\n", a);
    printf("file(%s) line(%d)\n", __FILE__, __LINE__);
    printf("current time(%s)\n", __TIME__);
#line 22 "line2.c"
    printf("\n\n");
    printf("file(%s) line(%d)\n", __FILE__, __LINE__);
    printf("current time(%s)\n", __TIME__);
    return 0;
}
[root@localhost c]# ./line
a= 5
file(line1.c) line(11)
current time(10:19:08)


file(line2.c) line(23)
current time(10:19:08)

2、此功能十分坑人,慎用

示例代码

#include <stdio.h>
int test1()
{
    int a = 5;
#line 10 "line1.c"
    printf("a= %d\n", a);
    printf("file(%s) line(%d)\n", __FILE__, __LINE__);
    printf("current time(%s)\n", __TIME__);
#line 22 "line2.c"
    printf("\n\n");
    printf("file(%s) line(%d)\n", __FILE__, __LINE__);
    printf("current time(%s)\n", __TIME__);
    return 0;
}

int test2()
{
     printf("in file(%s) line(%d)\n", __FILE__, __LINE__);
     return 0;
}
int main()
{
    test1();
    test2();
    return 0;
}

从打印上看,貌似正常

[root@localhost c]# ./line
a= 5
file(line1.c) line(11)
current time(11:26:23)


file(line2.c) line(23)
current time(11:26:23)
in file(line2.c) line(30)
[root@localhost c]#

但是想要gdb调试就不行了,一来提示找不到line2.c,二来即使注释掉文件名,行号也会在GDB中错位,体验太差

二十、re2c工具,将正则表达式转化为C代码

1、简介

将正则表达式,转化为C代码

2、资源

re2c-1.1.1.tar.gz

3、安装

sh autogen.sh
./configure
make & make install

4、用法

A、代码中自带18 个用例

[root@localhost examples]# pwd
/packages/re2c-1.1.1/examples
[root@localhost examples]# ls
01_recognizing_integers.i.c           10_uri_rfc3986.i--tags.c
01_recognizing_integers.i.re          10_uri_rfc3986.i--tags.re
02_recognizing_strings.i.c            11_http_rfc7230.i--tags.c
02_recognizing_strings.i.re           11_http_rfc7230.i--tags.re
03_arbitrary_large_input.i.c          13_records.i--tags.c
03_arbitrary_large_input.i.re         13_records.i--tags.re
04_parsing_integers_blocks.i.c        14_options.i--tags.c
04_parsing_integers_blocks.i.re       14_options.i--tags.re
05_parsing_integers_conditions.ci.c   15_binsyms.i--input(custom).c
05_parsing_integers_conditions.ci.re  15_binsyms.i--input(custom).re
06_braille.cr8i.c                     16_fake_sentinel.i--input(custom).c
06_braille.cr8i.re                    16_fake_sentinel.i--input(custom).re
07_cxx98.i.c                          17_ifstream.i--input(custom).c
07_cxx98.i.re                         17_ifstream.i--input(custom).re
08_ipv4.i--tags.c                     18_push_model.if.c
08_ipv4.i--tags.re                    18_push_model.if.re
09_etc_passwd.i--tags.c               a.out
09_etc_passwd.i--tags.re
[root@localhost examples]#

B、拿第一个例子做研究

i、转化为.c操作

利用正则表达式,生成 .c 文件
[root@localhost examples]# re2c 01_recognizing_integers.i.re > 01_recognizing_integers.i.c
[root@localhost examples]#:

ii、正则表达式的内容

#include <stdio.h>
enum num_t { ERR, BIN, OCT, DEC, HEX };
static num_t lex(const char *YYCURSOR)
{
    const char *YYMARKER;
    /*!re2c
        re2c:define:YYCTYPE = char;
        re2c:yyfill:enable = 0;

        end = "\x00";
        bin = '0b' [01]+;
        oct = "0" [0-7]*;
        dec = [1-9][0-9]*;
        hex = '0x' [0-9a-fA-F]+;

        *       { return ERR; }
        bin end { return BIN; }
        oct end { return OCT; }
        dec end { return DEC; }
        hex end { return HEX; }
    */
}
int main(int argc, char **argv)
{
    for (int i = 1; i < argc; ++i) {
        switch (lex(argv[i])) {
            case ERR: printf("error\n"); break;
            case BIN: printf("binary\n"); break;
            case OCT: printf("octal\n"); break;
            case DEC: printf("decimal\n"); break;
            case HEX: printf("hexadecimal\n"); break;
        }
    }
    return 0;
}

iii、由正则表达式生成的.c文件

/* Generated by re2c 1.1.1 on Mon Jun 10 11:57:20 2019 */
#line 1 "01_recognizing_integers.i.re"
#include <stdio.h>

enum num_t { ERR, BIN, OCT, DEC, HEX };

static num_t lex(const char *YYCURSOR)
{
    const char *YYMARKER;

#line 12 "<stdout>"
{
        char yych;
        yych = *YYCURSOR;
        switch (yych) {
        case '0':       goto yy4;
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':       goto yy5;
        default:        goto yy2;
        }
yy2:
        ++YYCURSOR;
yy3:
#line 18 "01_recognizing_integers.i.re"
        { return ERR; }
#line 34 "<stdout>"
yy4:
        yych = *(YYMARKER = ++YYCURSOR);
        switch (yych) {
        case 0x00:      goto yy6;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':       goto yy8;
        case 'B':
        case 'b':       goto yy11;
        case 'X':
        case 'x':       goto yy12;
        default:        goto yy3;
        }
yy5:
        yych = *(YYMARKER = ++YYCURSOR);
        switch (yych) {
        case 0x00:      goto yy13;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':       goto yy15;
        default:        goto yy3;
        }
yy6:
        ++YYCURSOR;
#line 20 "01_recognizing_integers.i.re"
        { return OCT; }
#line 73 "<stdout>"
yy8:
        yych = *++YYCURSOR;
        switch (yych) {
        case 0x00:      goto yy6;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':       goto yy8;
        default:        goto yy10;
        }
yy10:
        YYCURSOR = YYMARKER;
        goto yy3;
yy11:
        yych = *++YYCURSOR;
        if (yych <= 0x00) goto yy10;
        goto yy18;
yy12:
        yych = *++YYCURSOR;
        if (yych <= 0x00) goto yy10;
        goto yy20;
yy13:
        ++YYCURSOR;
#line 21 "01_recognizing_integers.i.re"
        { return DEC; }
#line 103 "<stdout>"
yy15:
        yych = *++YYCURSOR;
        switch (yych) {
        case 0x00:      goto yy13;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':       goto yy15;
        default:        goto yy10;
        }
yy17:
        yych = *++YYCURSOR;
yy18:
        switch (yych) {
        case 0x00:      goto yy21;
        case '0':
        case '1':       goto yy17;
        default:        goto yy10;
        }
yy19:
        yych = *++YYCURSOR;
yy20:
        switch (yych) {
        case 0x00:      goto yy23;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':       goto yy19;
        default:        goto yy10;
        }
yy21:
        ++YYCURSOR;
#line 19 "01_recognizing_integers.i.re"
        { return BIN; }
#line 162 "<stdout>"
yy23:
        ++YYCURSOR;
#line 22 "01_recognizing_integers.i.re"
        { return HEX; }
#line 167 "<stdout>"
}
#line 23 "01_recognizing_integers.i.re"

}

int main(int argc, char **argv)
{
    for (int i = 1; i < argc; ++i) {
        switch (lex(argv[i])) {
            case ERR: printf("error\n"); break;
            case BIN: printf("binary\n"); break;
            case OCT: printf("octal\n"); break;
            case DEC: printf("decimal\n"); break;
            case HEX: printf("hexadecimal\n"); break;
        }
    }
    return 0;
}

iv、对生成的.c文件编译执行,功能极其完美

[root@localhost examples]# g++ 01_recognizing_integers.i.c
[root@localhost examples]# ./a.out
[root@localhost examples]# ./a.out 33
decimal
[root@localhost examples]# ./a.out 0x33
hexadecimal
[root@localhost examples]# ./a.out 033
octal
[root@localhost examples]# ./a.out 0b1101
binary
[root@localhost examples]#

二十一、常用重要函数

1、linux对字符串不区分大小写比较

strncasecmp(strall, substring, len);

2、字符串提取,%*s忽略匹配字符串,但是无法跳过空格

[15900.DESKTOP-CGPRCF3] ➤ cat my_scanf.c
#include <stdio.h>
#include <string.h>

int main()
{
    char str[] = "227 Entering Passive Mode (10,63,197,91,148,177).ssd\r\n";
    int ip1 = 0;
    int ip2 = 0;
    int ip3 = 0;
    int ip4 = 0;
    int port1 = 0;
    int port2 = 0;

    int total = 0;
    int used  = 0;
    int free  = 0;
    int buffers=0;

    char *buf = "Mem: 1004608K total, 414696k used, 589912k free 81872k buffers";
    sscanf(buf, "%*s%d%*s%*s%d%*s%*s%d%*s%*s%d%*s%*s", &total, &used, &free, &buffers);
    printf("total=%d used=%d free=%d buffers=%d\n",  total, used, free, buffers);
    return 0;
}

[15900.DESKTOP-CGPRCF3] ➤ ./my_scanf.exe
total=1004608 used=414696 free=589912 buffers=81872

3、正则表达式(一个简单典型的应用)

C语言正则表达式详解.html

#include <stdio.h>
#include <sys/types.h>
#include <regex.h>

int main(int argc, char **argv)
{
    int status , i;
    int cflags = REG_EXTENDED;
    regmatch_t    pmatch[1];
    const size_t nmatch = 1;
    regex_t      reg;
    const char  *pattern="^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\w+([-.]\\w+)*$";
    char        *buf    ="99.david_19842003@gmail.com.cn";

    regcomp(\®, pattern, cflags);
    status = regexec(®, buf, nmatch, pmatch, 0);
    if (status == REG_NOMATCH)
    {
        printf(" no match\n");
    }
    else if (0 == status)
    {
        printf("match:\n");
        for (i = pmatch[0].rm_so; i < pmatch[0].rm_eo; i++)
        {
            putchar(buf[i]);
        }
        printf("\n");
    }
    regfree(\®);
    return 0;
}


[wishcell@localhost study]$ gcc -g regular.c -o regular
[wishcell@localhost study]$ ./regular
match:
99.david_19842003@gmail.com.cn
[wishcell@localhost study]$

二十二、C语言操作环境变量

char *pEnv = getenv("PATH");
char *path = (char *)malloc(strlen(pEnv) + 10);
strcat(path, "PATH=");
strcat(path, pEnv);
strcat(path, ":./JAVA_1.8/");
putenv(path);

二十三、当初颜士龙找我做的锁屏程序

1、锁屏的代码

A、源码

//LockScreen.c
//compile : gcc LockScreen.c -lUser32
#define WINVER 0X0500
#include <windows.h>

int WINAPI WinMain(HINSTANCE a, HINSTANCE b, LPSTR c, int d)
{
    MessageBox(NULL, 
               TEXT("还有10分钟到期,请继续充值"), 
               TEXT("充值提醒"),
               MB_OKCANCEL|MB_ICONQUESTION);

    if (IDOK == MessageBox(NULL, 
                         TEXT("还有10分钟到期,请继续充值"), 
                         TEXT("充值提醒"),
                         MB_OKCANCEL|MB_ICONQUESTION))
    {
        if (!LockWorkStation())
        {
            TCHAR* szErr = {0};
            wsprintf(szErr, "LockWorkStation failed with %d\n", GetLastError());
            MessageBox(NULL, szErr, "Error", MB_OK|MB_ICONSTOP);
        }
    }
    return 0;
}

B、编译环境 msys

$ gcc -v
Using built-in specs.
COLLECT_GCC=C:\MinGW\bin\gcc.exe
COLLECT_LTO_WRAPPER=c:/mingw/bin/../libexec/gcc/mingw32/6.3.0/lto-wrapper.exe
Target: mingw32
Configured with: ../src/gcc-6.3.0/configure --build=x86_64-pc-linux-gnu --host=mingw32 --target=mingw32 --with-gmp=/mingw --with-mpfr --with-mpc=/mingw --with-isl=/mingw --prefix=/mingw --disable-win32-registry --with-arch=i586 --with-tune=generic --enable-languages=c,c++,objc,obj-c++,fortran,ada --with-pkgversion='MinGW.org GCC-6.3.0-1' --enable-static --enable-shared --enable-threads --with-dwarf2 --disable-sjlj-exceptions --enable-version-specific-runtime-libs --with-libiconv-prefix=/mingw --with-libintl-prefix=/mingw --enable-libstdcxx-debug --enable-libgomp --disable-libvtv --enable-nls
Thread model: win32
gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)

15900@DESKTOP-CGPRCF3 /e/xunlei/study/C/LockScreen

C、编译方法

$ gcc LockScreen.c -o LockScreen -luser32
LockScreen.c:1:11: error: expected '=', ',', ';', 'asm' or '__attribute__' before '.' token
 LockScreen.c
           ^
In file included from c:\mingw\include\windows.h:41:0,
                 from LockScreen.c:4:
c:\mingw\lib\gcc\mingw32\6.3.0\include\stdarg.h:99:9: error: unknown type name '__gnuc_va_list'
 typedef __gnuc_va_list va_list;
         ^~~~~~~~~~~~~~

15900@DESKTOP-CGPRCF3 /e/xunlei/study/C/LockScreen
$ dir LockScreen.exe
LockScreen.exe

D、一运行,弹出对话框,点击之后,直接锁屏

2、程序改良,因为上面这段代码有BUG,用户必须点确定才能锁屏

当时因为这一点缺陷,一直没有办法完成任务,也不敢跟人家确定。今天看来,这是一个极其简单的工作。网吧管理服务器与客户端通信,随便搞个程序都行。然后到时间提前10分钟,弹出提醒,说要锁屏,时间一到,直接锁屏,根本不需要等用户点确定
改良之后的代码,原来如此简单:

//compile : gcc LockScreen.c -lUser32
#define WINVER 0X0500
#include <windows.h>

int WINAPI WinMain(HINSTANCE a, HINSTANCE b, LPSTR c, int d)
{
    MessageBox(NULL, 
               TEXT("还有10分钟到期,请继续充值"), 
               TEXT("充值提醒"),
               MB_OKCANCEL|MB_ICONQUESTION);

    //sleep(10); //这里可以 sleep 几秒钟,几分钟都行
//  if (IDOK == MessageBox(NULL, 
//                       TEXT("还有10分钟到期,请继续充值"), 
//                       TEXT("充值提醒"),
//                       MB_OKCANCEL|MB_ICONQUESTION))
//  {
        if (!LockWorkStation())
        {
            TCHAR* szErr = {0};
            wsprintf(szErr, "LockWorkStation failed with %d\n", GetLastError());
            MessageBox(NULL, szErr, "Error", MB_OK|MB_ICONSTOP);
        }
//  }
    return 0;
}

3、当时思路复杂,想把用户的键盘输入截获,让他输入无效,这是分析文档

一个简单的键盘钩子程序
    实现适时监视键盘,并将按键信息保存在TXT文件中的程序 
 Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。
 而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,
 来完成普通应用程序难以实现的功能。钩子的种类很多,每种钩子可以截获并处理相应的消息,
 如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。
 本文在VC6编程环境下实现了一个简单的键盘钩子程序,并对Win32全局钩子的运行机制、
 Win32 DLL的特点、VC6环境下的MFC DLL以及共享数据等相关知识进行了简单的阐述。

一.Win32全局钩子的运行机制
    钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。
    每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。
    这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
    对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,
    也就是后加入的先获得控制权。要实现Win32的系统钩子,必须调用SDK中的API函数SetWindowsHookEx来
    安装这个钩子函数,这个函数的原型是
        HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);

    其中,
        第一个参数是钩子的类型;
        第二个参数是钩子函数的地址;
        第三个参数是包含钩子函数的模块句柄;
        第四个参数指定监视的线程。
    如果指定确定的线程,即为线程专用钩子;如果指定为空,即为全局钩子。
    其中,全局钩子函数必须包含在DLL(动态链接库)中,而线程专用钩子还可以包含在可执行文件中。
    得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个SDK中
    的API函数CallNextHookEx来传递它。钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。

    二.Win32 DLL的特点
        Win32 DLL与 Win16 DLL有很大的区别,这主要是由操作系统的设计思想决定的。
        一方面,在Win16 DLL中程序入口点函数和出口点函数(LibMain和WEP)是分别实现的;
        而在Win32 DLL中却由同一函数DLLMain来实现。无论何时,当一个进程或线程载入和卸载DLL时,
        都要调用该函数,它的原型是
            BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);

        其中,
            第一个参数表示DLL的实例句柄;
            第三个参数系统保留;

        这里主要介绍一下第二个参数,
        它有四个可能的值:
            DLL_PROCESS_ATTACH(进程载入),
            DLL_THREAD_ATTACH(线程载入),
            DLL_THREAD_DETACH(线程卸载),
            DLL_PROCESS_DETACH(进程卸载),
        在DLLMain函数中可以对
        传递进来的这个参数的值进行判别,并根据不同的参数值对DLL进行必要的初始化或清理工作。
        举个例子来说,当有一个进程载入一个DLL时,系统分派给DLL的第二个参数为DLL_PROCESS_ATTACH,
        这时,你可以根据这个参数初始化特定的数据。另一方面,在Win16环境下,所有应用程序都在同一地址空间;
        而在Win32环境下,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,
        这减少了应用程序间的相互影响,但同时也增加了编程的难度。大家知道,在Win16环境中,
        DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,
        当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,而且也复制该DLL的全局数据的
        一份拷贝到该进程空间,也就是说每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同的。
        因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。亦即把这些需要共享的
        数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。

    三.VC6中MFC DLL的分类及特点
        在VC6中有三种形式的MFC DLL(在该DLL中可以使用和继承已有的MFC类)可供选择,
        即Regular statically linked to MFC DLL(标准静态链接MFC DLL)和Regular using the shared 
        MFC DLL(标准动态链接MFC DLL)以及Extension MFC DLL(扩展MFC DLL)。
        第一种DLL的特点是,在编译时把使用的MFC代码加入到DLL中,因此,在使用该程序时不需要其他MFC
        动态链接类库的存在,但占用磁盘空间比较大;第二种DLL的特点是,在运行时,动态链接到MFC类库,
        因此减少了空间的占用,但是在运行时却依赖于MFC动态链接类库;这两种DLL既可以被MFC程序使用也
        可以被Win32程序使用。第三种DLL的特点类似于第二种,做为MFC类库的扩展,只能被MFC程序使用。

    四.在VC6中全局共享数据的实现
        在主文件中,用#pragma data_seg建立一个新的数据段并定义共享数据,其具体格式为:
            #pragma data_seg ("shareddata")
            HWND sharedwnd=NULL;//共享数据
            #pragma data_seg()

        仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现
        该目的(其效果是相同的),一种方法是在.DEF文件中加入如下语句:
            SETCTIONS
            shareddata READ WRITE SHARED

      另一种方法是在项目设置链接选项中加入如下语句:
            /SECTION:shareddata,rws

      五.具体实现步骤
        由于全局钩子函数必须包含在动态链接库中,所以本例由两个程序体来实现。
        1.建立钩子KeyboardHook.dll
            (1)选择MFC AppWizard(DLL)创建项目Mousehook;
            (2)选择MFC Extension DLL(共享MFC拷贝)类型;
            (3)由于VC6没有现成的钩子类,所以要在项目目录中创建KeyboardHook.h文件,在其中建立钩子类:

                class AFX_EXT_CLASS CKeyboardHook : public CObject  
                {
                    public:
                          CKeyboardHook();//钩子类的构造函数
                          virtual ~CKeyboardHook();//钩子类的析构函数

                    public:
                          BOOL StartHook(); //安装钩子函数
                          BOOL StopHook();//卸载钩子函数 
                };

          (4)在KeyboardHook.cpp文件的顶部加入#include "KeyboardHook.h"语句;
          (5)在KeyboardHook.cpp文件的顶部加入全局共享数据变量:
                #pragma data_seg("mydata")
                      HHOOK glhHook=NULL;         //安装的鼠标勾子句柄 
                      HINSTANCE glhInstance=NULL; //DLL实例句柄
                #pragma data_seg()

        (6)在DEF文件中定义段属性:
                SECTIONS
                mydata READ WRITE SHARED

      (7)在主文件KeyboardHook.cpp的DllMain函数中加入保存DLL实例句柄的语句:
            glhInstance=hInstance;//插入保存DLL实例句柄

      (8)键盘钩子函数的实现:
            //键盘钩子函数
            LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
            {
                  char ch=0;
                  FILE *fl;
                  if( ((DWORD)lParam&0x40000000) && (HC_ACTION==nCode) ) //有键按下
                  {
                        if( (wParam==VK_SPACE)||(wParam==VK_RETURN)||(wParam>=0x2f ) &&(wParam<=0x100) )
                        {
                              fl=fopen("key.txt","a+");    //输出到key.txt文件
                              if (wParam==VK_RETURN)
                              {
                                    ch=' ';
                              }
                              else
                              {
                                    BYTE ks[256];
                                    GetKeyboardState(ks);
                                    WORD w;
                                    UINT scan=0;
                                    ToAscii(wParam,scan,ks,&w,0);
                                    //ch=MapVirtualKey(wParam,2); //把虚键代码变为字符
                                    ch =char(w); 
                              }
                              fwrite(&ch, sizeof(char), 1, fl);
                        }
                        fclose(fl);
                  }
                  return CallNextHookEx( glhHook, nCode, wParam, lParam ); 
            }

        (9)类CKeyboardHook的成员函数的具体实现:
            CKeyboardHook::CKeyboardHook()
            {
            }

            CKeyboardHook::~CKeyboardHook()
            {
                  if(glhHook)
                        UnhookWindowsHookEx(glhHook);
            }

            //安装钩子并设定接收显示窗口句柄
            BOOL CKeyboardHook::StartHook()
            { 
                  BOOL bResult=FALSE;
                  glhHook=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,glhInstance,0);
                  /*============================================================
                  HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId )
                  参数idHook表示钩子类型,它是和钩子函数类型一一对应的。
                  比如,WH_KEYBOARD表示安装的是键盘钩子,WH_MOUSE表示是鼠标钩子等等。 

                  Lpfn是钩子函数的地址。 

                  HMod是钩子函数所在的实例的句柄。对于线程钩子,该参数为NULL;对于系统钩子,
                  该参数为钩子函数所在的DLL句柄。 

                  dwThreadId 指定钩子所监视的线程的线程号。对于全局钩子,该参数为NULL。 

                  SetWindowsHookEx返回所安装的钩子句柄。
                  值得注意的是线程钩子和系统钩子的钩子函数的位置有很大的差别。
                  线程钩子一般在当前线程或者当前线程派生的线程内,
                  而系统钩子必须放在独立的动态链接库中,实现起来要麻烦一些。
                  ===========================================================*/
                  if(glhHook!=NULL)
                  bResult=TRUE;
                  return bResult; 
            }

            //卸载钩子
            BOOL CKeyboardHook::StopHook() 
            {
                  BOOL bResult=FALSE;
                  if(glhHook)
                  {
                        bResult= UnhookWindowsHookEx(glhHook);
                        if(bResult)
                              glhHook=NULL;
                  }
                  return bResult;
            }

      (10)编译项目生成KeyboardHook.dll。

      2.创建钩子可执行程序
      (1)用MFC的AppWizard(EXE)创建项目KeyHook;
      (2)选择“基于对话应用”并按下“完成”键;
      (3)在KeyHookDlg.h中加入包含语句#include "KeyboardHook.h";
      (4)在KeyHookDlg.h中添加私有数据成员:
            CKeyboardHook m_hook;//加入钩子类作为数据成员
      (5)链接DLL库,即把../KeyboardHook.lib加入到项目设置链接标签中;
      (6)把OK按钮ID改为ID_HOOK,写实现代码:
            void CKeyHookDlg::OnHook()
            {
                  m_hook.StartHook(); 
            }

      (7)关闭按钮实现:
            void CKeyHookDlg::OnCancel() 
            {
                  m_hook.StopHook();
                  CDialog::OnCancel();
            }

      (8)编译项目生成可执行文件;
        运行生成的KeyHook.exe程序,按HOOK!按钮,加载钩子后按下键盘上的一些键,
        可以发现EXE目录下自动生成了一个key.txt文件,该文件记载了你的按键信息

4、当初做的测试文件,编译没有成功

#include <Windows.h>
HHOOK hkey=NULL;
HINSTANCE h_dll;

//#pragma data_seg(".MySec")  //定义字段,段名.MySec
HWND h_wnd=NULL;
//#pragma data_seg()
//#pragma comment(linker,"/section:.MySec,RWS")

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
    h_dll=hinstDLL;
//  MessageBox(0,"运行dllman","",MB_OK);
    return TRUE;
}

LRESULT CALLBACK my_test(int nCode, WPARAM wParam, LPARAM iParam)//
{
/*
    if(nCode==HC_ACTION)
    {
        MessageBox(0,"成功!!","标题",MB_OK);
    }
    else
    {
        MessageBox(0,"失败!!","标题",MB_OK);
    }
*/
    MessageBox(0,"被截取","",MB_OK);
    UnhookWindowsHookEx(hkey);
    return 1;
}

void SetHook(HWND hwnd)
{
    mod = LoadLibrary("foo.dll");
    h_wnd = hwnd;
//  MessageBox(0,"运行sethook","",MB_OK);
    hkey=SetWindowsHookEx(WH_JOURNALRECORD, my_test, mod, SYSTEM_HK): 
}

二十四、我学习 HTTP 的过程,从最基础知识开始累积

##1、第一课,有人访问,就直接返回静太内容,在页面显示

用法:
H:\backup_disk\HateIron\code\c\TwoFilesModule\http\first>gcc simple_http.c -lwsock32 -o mysocket
H:\backup_disk\HateIron\code\c\TwoFilesModule\http\first>mysocket.exe
GET / HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1574251766,1575385558; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2218234920%22%2C%22%24device_id%22%3A%2216e88b7a0cd5f-0b283a9aea47a08-4c302b7a-788544-16e88b7a0ce32a%22%2C%22props%22%3A%7B%7D%2C%22first_id%22%3A%2216e88b7a0cd5f-0b283a9aea47a08-4c302b7a-788544-16e88b7a0ce32a%22%7D; __yadk_uid=64drS4lKUGvSWNdOdUoYLjhtaMCxLsZC; Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=1574251813; HOST=http%3A//127.0.0.1/; APP_HOST=http%3A//127.0.0.1/; kodUserLanguage=zh-CN; kodVersionCheck=check-at-1575638213; kodUserID=1; HOLER-AUTH-TOKEN=da33d6f8cb934b9b90ac3414c34fe577; X-CSRF-TOKEN=7RZKKe9g48eMu8kwNmPJ; kod_updateIgnore_timeout=1575897005
Upgrade-Insecure-Requests: 1
核心代码解析:这实际上就是一个 socket 服务器,等待任意客户端来连接
     谁申请连接,就创建连接,然后给它发送一段 html 字符串,让它在浏览器上显示。
     然后关闭连接,继续等待下一个客户端请求连接....
    while(1)
    {
        csock = accept(sock,(struct sockaddr *)&client_ipaddr,&length);
        if(csock==SOCKET_ERROR)
        {
            printf("Listen error/n");
            WSACleanup();
            exit(1);
        }
        nbuff = recv(csock,buff,4095,0);
        buff[nbuff]='\0';
        printf("%s",buff);
        Sleep(50);
        send(csock,headers,strlen(headers),0);
        Sleep(50);
        send(csock,CustomHtml,strlen(CustomHtml),0);

        Sleep(50);
        closesocket(csock);
    }

2、第二课,开始进化,给客户端响应,返回一个固定的 html 文件,让它在终端浏览器显示

在上例的基础上,只是把固定的 buffer 改成了文件内容,核心代码:
    //////////加载要传送的主网页内容
    html_file_sz = load_file("index.html", html_file_buf);
    wsprintf(headers, hdrFmt, (const char*) strGmtNow, html_file_sz);

3、第三课,加载指定路径的文件

4、实践失败

5、第五课,服务器的不同 html 文件之间相互链接跳转

核心代码分析:
    关键在于动态解析浏览器转送回来的请求字符串,如果是请求打开新文件,会把文件名放在请求内容中,解析出来,
然后把文件内容放到 buffer 中发送出去即可

    //analyse client request  解析请求中拾携带的文件名
    type = AnalyseClientReq(buff, file_name);
    if (REQ_GET == type)
    {
        html_file_sz = load_file(file_name, html_file_buf);
        wsprintf(headers, hdrFmt, (const char*) strGmtNow, html_file_sz);
    }

    具体的解析函数,这里采用了一种非常简单、想当然的办法来解析,只是为了验证简单的内部链接:
int AnalyseClientReq(char *req, char *out)
{
    if (0 == strncmp(req, "GET", 3))
    {
        parse_GET_req(req, out);
        /*
        printf("this is a GET req\n");
        path = req+4;

        spc_ptr = strchr(path, ' ');
        path_sz = spc_ptr - path;

        if ((0 == strcmp(out, "/")) || (path_sz == 1))
        {
            strcpy(out, "index.html");
        }
        else
        {
            path++; //skip '/'
            memcpy(out, path, path_sz-1);
            str_substitude(out, '/', '\\');
        }
        */
        return REQ_GET;
    }
    else if (0 == strncmp(req, "POST", 4))
    {
        if (0 == parse_POST_req(req, out))
            return POST_PARSE_ERR;

        return REQ_POST;
    }

    return REQ_ERR;
}

6、6——10,所有相关学习改进小结

first:
    能够将 HTML 文件流返回给浏览器,并正常显示。
    采用固定字符串的形式发送。实现最基本功能
second:
    改进:将固定数组字符串,改为从真实的 html 文件中加载。
          然后把加载好的文件发送给浏览器并显示。
          这加可以方便地更改主页内容
third:
    改进:将主页放到指定目录是。并从目录中加载。
          实现目录结构,层次清析。
forth:
    改进:实现链接文件的显示。
          实现网面目录内文件跳转
    技术上是对 GET 进行了简单的解析。分析出不同的文件路径
fifth:
    改进:实现对 GET / POST 分别处理
sixth:
    改进:实现 Ajax GET 技术。
          实现对 AJAX GET 携带参数的情况进行处理。能够解析出参数
seven:
    改进:将服务器对 AJAX 的请求响应时间放长。为 5 秒。
eight:
    改进:
        1、服务器对POS 的响应,发送给浏览器的数据为一个 TABLE,浏览器利用 xmlhttp 能够显示
        2、浏览器调用 xmlhttp.send("fname=Bill lname=Gates"); 方法,发送给服务器的,就是引号中的字符串,
           其格式没有任何变化。这里将两个参数中间的 & 符号去掉,发现服务器照样可以处理
nine:
        C代码改动很小。
        把网页/JAVASCRIPT 基础知识好好学习了一遍.
        加入大量网页例子
ten:

二十五、socket 编程

##1、最基础的,一问一答式,如同 QQ聊天原始版一样
server.c
client.c

用法: gcc server.c -o server -lwsock32
//server端的程序
#include "winsock.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include <windows.h>

struct pointindex
{
    int firstDemension;
    int secondDemension;
    int thirdDemension;
    int forthDemension;
    int value;
    char fileName[10];
};
typedef struct pointindex PointIndex;
BOOL InitWinsock()
{
    int     Error;
    WORD    VersionRequested;
    WSADATA WsaData;

    VersionRequested = MAKEWORD(2,2);
    Error = WSAStartup(VersionRequested, &WsaData); //启动WinSock2

    if(Error!=0)
    {
        return FALSE;
    }
    else
    {
        if(LOBYTE(WsaData.wVersion)!=2||HIBYTE(WsaData.wHighVersion)!=2)
        {
            WSACleanup();
            return FALSE;
        }
    }
    return TRUE;
}

int main()
{
    SOCKET socket1;

    int    length=sizeof(PointIndex);
    struct sockaddr_in local;
    struct sockaddr_in from;
    int    fromlen = sizeof(from);

    InitWinsock();  
    local.sin_family      = AF_INET;
    local.sin_port        = htons(1000); ///监听端口
    local.sin_addr.s_addr = inet_addr("127.0.0.1"); ///本机
    socket1=socket(AF_INET, SOCK_DGRAM, 0);

    bind(socket1,(struct sockaddr*)&local,sizeof local);
    char buffer[60]={0};

    while (1)
    {
        //printf("Recieve message :  ");
        if (recvfrom(socket1, buffer, length, 0,(struct sockaddr*)&from,&fromlen)!=SOCKET_ERROR)
        {
            PointIndex *ptest = (PointIndex *)buffer;
            printf("recieved buff:%s\n",buffer);
            printf("Send message :  ");
            gets(buffer);
            sendto(socket1, buffer, length, 0, (struct sockaddr*)&from, fromlen);
        }
        Sleep(500);
    }
    closesocket(socket1);
    return 0;
}
#include "winsock.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include <windows.h>

struct pointindex
{
    int firstDemension;
    int secondDemension;
    int thirdDemension;
    int forthDemension;
    int value;
    char fileName[10];
};
typedef struct pointindex PointIndex;
BOOL InitWinsock();
int main()
{
    int length=0;
    SOCKET socket1;
    PointIndex point;

    struct sockaddr_in server;
    int len =sizeof(server);

    InitWinsock();
    server.sin_family=AF_INET;
    server.sin_port=htons(1000); ///server的监听端口
    server.sin_addr.s_addr=inet_addr("127.0.0.1"); ///server的地址
    socket1=socket(AF_INET,SOCK_DGRAM,0);

    while (1)
    {
        point.firstDemension=1;
        point.secondDemension=2;
        point.thirdDemension=3;
        point.forthDemension=4;
        point.value=0;
        strcpy(point.fileName,"test");
        PointIndex *ppoint = &point;
        length=sizeof(PointIndex);
        char *buffer = (char*)(ppoint);
        printf("Send message :  ");
        gets(buffer);
        if (sendto(socket1, buffer, length, 0, (struct sockaddr*)&server, len)!=SOCKET_ERROR)
        {
            if (recvfrom(socket1,buffer,length,0,(struct sockaddr*)&server,&len)!=SOCKET_ERROR)
                printf("Recieve message :  %s\n",buffer);
        }
    }
    closesocket(socket1);
    return 0;
}

BOOL InitWinsock()
{
    int Error;
    WORD VersionRequested;
    WSADATA WsaData;
    VersionRequested=MAKEWORD(2,2);
    Error=WSAStartup(VersionRequested,&WsaData); //启动WinSock2
    if(Error!=0)
    {
        return FALSE;
    }
    else
    {
        if(LOBYTE(WsaData.wVersion)!=2||HIBYTE(WsaData.wHighVersion)!=2) 
        {
            WSACleanup();
            return FALSE;
        }
    }
    return TRUE;
}


##2、简单的 socket 通信,可以作为其它开发的基础
server.c
client.c

二十六、链接选项-rdynamic专题

gcc选项-g与-rdynamic的异同
 来源:
http://www.tuicool.com/articles/EvIzUn
https://www.cnblogs.com/LiuYanYGZ/p/5550544.html

gcc选项-g与-rdynamic的异同

gcc 的 -g ,应该没有人不知道它是一个调试选项,因此在一般需要进行程序调试的场景下,我们都会加上该选项,并且根据调试工具的不同,还能直接选择更有针对性的说明,比如 -ggdb 。-g是一个编译选项,即在源代码编译的过程中起作用,让gcc把更多调试信息(也就包括符号信息)收集起来并将存放到最终的可执行文件内。 
相比-g选项, -rdynamic 却是一个 连接选项 ,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表(即.dynsym表)里,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。

看示例:

[root@www c]# cat t.c
#include <stdio.h>
void bar() {}
void baz() {}
void foo() {}
int main() { foo(); printf("test"); return 0; }

对于上面的示例代码,普通和加-g编译:

[root@www c]# uname -a
Linux www.t1.com 2.6.38.8 #2 SMP Wed Nov 2 07:52:53 CST 2011 x86_64 x86_64 x86_64 GNU/Linux
[root@www c]# gcc -O0 -o t t.c
[root@www c]# gcc -O0 -g -o t.g t.c
[root@www c]# readelf -a t > t.elf
[root@www c]# readelf -a t.g > t.g.elf
[root@www c]# ls -lh *.elf t t.g
-rwxr-xr-x. 1 root root 6.6K Jul 24 06:50 t
-rw-r--r--. 1 root root  15K Jul 24 06:51 t.elf
-rwxr-xr-x. 1 root root 7.9K Jul 24 06:50 t.g
-rw-r--r--. 1 root root  16K Jul 24 06:51 t.g.elf

加-g编译后,因为包含了debug信息,因此生成的可执行文件偏大(程序本身非常小,所以增加的调试信息不多)。 
看-g编译的符号表:

[root@www c]# readelf -s t

Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)

Symbol table '.symtab' contains 67 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
...
    48: 00000000004003e0     0 FUNC    GLOBAL DEFAULT   13 _start
    49: 00000000004004c4     6 FUNC    GLOBAL DEFAULT   13 bar
...
    53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND putchar@@GLIBC_2.2.5
    54: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    55: 00000000004005e8     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    56: 00000000004004d0     6 FUNC    GLOBAL DEFAULT   13 foo
...
    64: 00000000004004d6    31 FUNC    GLOBAL DEFAULT   13 main
    65: 0000000000400390     0 FUNC    GLOBAL DEFAULT   11 _init
    66: 00000000004004ca     6 FUNC    GLOBAL DEFAULT   13 baz

注意.dynsym表,只有该程序用到的几个外部动态符号存在。 
加-rdynamic选项编译,readelf查看:

[root@www c]# gcc -O0 -rdynamic -o t.rd t.c
[root@www c]# readelf -s t.rd 

Symbol table '.dynsym' contains 20 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     5: 0000000000400724     6 FUNC    GLOBAL DEFAULT   13 bar
     6: 0000000000400730     6 FUNC    GLOBAL DEFAULT   13 foo
     7: 0000000000600b68     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
     8: 0000000000600b80     0 NOTYPE  GLOBAL DEFAULT  ABS _end
     9: 0000000000600b6c     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
    10: 0000000000600b68     0 NOTYPE  WEAK   DEFAULT   24 data_start
    11: 0000000000400640     0 FUNC    GLOBAL DEFAULT   13 _start
    12: 0000000000400848     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    13: 0000000000400770   137 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    14: 0000000000600b6c     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    15: 0000000000400736    39 FUNC    GLOBAL DEFAULT   13 main
    16: 00000000004005f0     0 FUNC    GLOBAL DEFAULT   11 _init
    17: 0000000000400760     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    18: 0000000000400838     0 FUNC    GLOBAL DEFAULT   14 _fini
    19: 000000000040072a     6 FUNC    GLOBAL DEFAULT   13 baz

Symbol table '.symtab' contains 67 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
...
    50: 0000000000400640     0 FUNC    GLOBAL DEFAULT   13 _start
    51: 0000000000400724     6 FUNC    GLOBAL DEFAULT   13 bar
...
    55: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND putchar@@GLIBC_2.2.5
    56: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    57: 0000000000400848     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    58: 0000000000400730     6 FUNC    GLOBAL DEFAULT   13 foo
...
    64: 0000000000400736    31 FUNC    GLOBAL DEFAULT   13 main
    65: 00000000004005f0     0 FUNC    GLOBAL DEFAULT   11 _init
    66: 000000000040072a     6 FUNC    GLOBAL DEFAULT   13 baz
[root@www c]#

可以看到添加-rdynamic选项后,.dynsym表就包含了所有的符号,不仅是已使用到的外部动态符号,还包括本程序内定义的符号,比如bar、foo、baz等。 
.dynsym表里的数据并不能被strip掉:

[root@www c]# strip t.rd
[root@www c]# readelf -s t.rd

Symbol table '.dynsym' contains 20 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     5: 0000000000400724     6 FUNC    GLOBAL DEFAULT   13 bar
     6: 0000000000400730     6 FUNC    GLOBAL DEFAULT   13 foo
     7: 0000000000600b68     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
     8: 0000000000600b80     0 NOTYPE  GLOBAL DEFAULT  ABS _end
     9: 0000000000600b6c     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
    10: 0000000000600b68     0 NOTYPE  WEAK   DEFAULT   24 data_start
    11: 0000000000400640     0 FUNC    GLOBAL DEFAULT   13 _start
    12: 0000000000400848     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    13: 0000000000400770   137 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    14: 0000000000600b6c     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    15: 0000000000400736    39 FUNC    GLOBAL DEFAULT   13 main
    16: 00000000004005f0     0 FUNC    GLOBAL DEFAULT   11 _init
    17: 0000000000400760     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    18: 0000000000400838     0 FUNC    GLOBAL DEFAULT   14 _fini
    19: 000000000040072a     6 FUNC    GLOBAL DEFAULT   13 baz

简单总结一下-g选项与-rdynamic选项的差别: 
1,-g选项新添加的是调试信息(一系列.debug_xxx段),被相关调试工具,比如gdb使用,可以被strip掉。

2,-rdynamic选项新添加的是动态连接符号信息,用于动态连接功能,比如dlopen()系列函数、backtrace()系列函数使用,不能被strip掉,即强制strip将导致程序无法执行:

[root@www c]# ./t.rd
test[root@www c]# strip -R .dynsym t.rd
[root@www c]# ./t.rd
./t.rd: relocation error: ./t.rd: symbol , version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference
[root@www c]#

3,.symtab表在程序加载时会被加载器 丢弃 ,gdb等调试工具由于可以直接访问到磁盘上的二进制程序文件:

[root@www c]# gdb t.g -q
Reading symbols from /home/work/dladdr/c/t.g...done.
(gdb)

因此可以使用所有的调试信息,这包括.symtab表;而backtrace()系列函数作为程序执行的逻辑功能,无法去读取磁盘上的二进制程序文件,因此只能使用.dynsym表。 
其它几个工具可以动态指定查看,比如nm、objdump:

[root@www c]# nm t.rd
nm: t.rd: no symbols
[root@www c]# nm -D t.rd
0000000000400848 R _IO_stdin_used
                 w _Jv_RegisterClasses
0000000000600b6c A __bss_start
0000000000600b68 D __data_start
                 w __gmon_start__
0000000000400760 T __libc_csu_fini
0000000000400770 T __libc_csu_init
                 U __libc_start_main
0000000000600b6c A _edata
0000000000600b80 A _end
0000000000400838 T _fini
00000000004005f0 T _init
0000000000400640 T _start
0000000000400724 T bar
000000000040072a T baz
0000000000600b68 W data_start
0000000000400730 T foo
0000000000400736 T main
                 U printf
[root@www c]#
[root@www c]# objdump -T t.rd

t.rd:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 printf
0000000000000000  w   D  *UND*  0000000000000000              __gmon_start__
0000000000000000  w   D  *UND*  0000000000000000              _Jv_RegisterClasses
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 __libc_start_main
0000000000400724 g    DF .text  0000000000000006  Base        bar
0000000000400730 g    DF .text  0000000000000006  Base        foo
0000000000600b68 g    D  .data  0000000000000000  Base        __data_start
0000000000600b80 g    D  *ABS*  0000000000000000  Base        _end
0000000000600b6c g    D  *ABS*  0000000000000000  Base        _edata
0000000000600b68  w   D  .data  0000000000000000  Base        data_start
0000000000400640 g    DF .text  0000000000000000  Base        _start
0000000000400848 g    DO .rodata    0000000000000004  Base        _IO_stdin_used
0000000000400770 g    DF .text  0000000000000089  Base        __libc_csu_init
0000000000600b6c g    D  *ABS*  0000000000000000  Base        __bss_start
0000000000400736 g    DF .text  0000000000000027  Base        main
00000000004005f0 g    DF .init  0000000000000000  Base        _init
0000000000400760 g    DF .text  0000000000000002  Base        __libc_csu_fini
0000000000400838 g    DF .fini  0000000000000000  Base        _fini
000000000040072a g    DF .text  0000000000000006  Base        baz

4,-rdynamic选项不产生任何调试信息,因此在一般情况下,新增的附加信息比-g选项要少得多。除非是完全的静态连接,否则即便是没有加-rdynamic选项,程序使用到的外部动态符号,比如前面示例里的printf,也会被自动加入到.dynsym表。

完全参考: 
http://stackoverflow.com/questions/8623884/gcc-debug-symbols-g-flag-vs-linkers-rdynamic-option
    备案/许可证编号为:沪ICP备20016505号