C /C++ 小白实战总结

510 阅读4分钟

[TOC]

经过了一阵子的 c/c++ 的运用,总结下。

  1. IDE 使用:QTcreator,CodeBlock,VisualStudio2019,visual Studio Code,sourceInsight
  2. 工具的使用: XShell WinSCP BeyondCompare postman UltraEdit notepad++
  3. 涉及的技术点包括:qmake python makefile c/c++ 条件编译 平台编译 git 宏展开 流重定向
  4. 开源库的使用 jsoncpp openssl curl ffmpeg

技术总结

c++

  1. c++ 的写法其实 也是很符合java 开发者的理解了。面向对象的开发。

创建单例

static A* getInstance(){
    static A* a = nullptr;
    if(a == nullptr){
        a= new A();
    }
    return a;
}

是不是很简单。 2. 线程的创建

c 版本

void run_what(void*){
    printf("hello\n");
    return NULL;
}
pthread_t thread;
pthread_create(&thread,NULL,run_what,this);

c++ 版本

std::thread t([this](){ //lambda 表达式
    run_what(this);
});
t.detach();

Qt版本

QObject * obj = new QObject();// 初始化QObject
connect(this,SIGNAL(threadStart()),m_USB_TF,SLOT(activate()));// 槽与信号
//connect(this,&threadStart,this,&TestForm::back);
QThread* thread =new QThread(obj);
obj->moveToThread(thread);
thread->start();
emit threadStart();//发送信号
  1. 入参为二级指针

二级指针为外界传入,需要附上参数的值,一般在内部进行申请空间等。

void test(char** a,int* len){
    if(a ==nullptr){
        *len =0;
        return ;
    }
    *len = 5;
    *a = malloc(*len); //堆上内存
    char * tmp = "abcdef";
    memcpy(a,tmp,4); //c里面对于一个 已分配的内存进行赋值操作
    *a[*len-1]='\0';
}
  1. 宏的展开
#define Test "test"
// 下面做的目的就是为了 宏的展开为字符串
#define _STR_1(a) #a
#define _STR(a) _STR_1(a)
int main(){
    printf("%s",_STR(TEST));
    return 0;
}
  1. 输出

一般情况下,调用系统函数出现异常都会有 相应的错误码与错误信息

#include <errno.h>
#include <string.h>

FILE * fp = fopen("test.txt","w+");
if(fp ==NULL){
    printf("write file error,errno=%d,msg=[%s]",errno,strerror(errno));// 这里注意 需要使用系统函数 strerror进行输出
    perror("write file error\n");
}
#ifdef WIN32
#include <Windows.h>
#include <io.h>
#include <process.h>
#ifdef TEST
extern FILE* log_fp;
#else
#define  log_fp stdout
#endif

#define LOGD(_fmt, ...)  do {fprintf(log_fp, "[D][TXZ][%u][%u] " _fmt " [%s:%d]\n",(uint32_t)GetCurrentProcessId(),(uint32_t)GetCurrentThreadId(),##__VA_ARGS__, __FILE__,__LINE__);fflush(log_fp);} while(0)
#define LOGW(_fmt, ...)  do {fprintf(log_fp, "[W][TXZ][%u][%u] " _fmt " [%s:%d]\n",(uint32_t)GetCurrentProcessId(),(uint32_t)GetCurrentThreadId(),##__VA_ARGS__, __FILE__,__LINE__);fflush(log_fp);} while(0)
#define LOGE(_fmt, ...)  do {fprintf(log_fp, "[E][TXZ][%u][%u] " _fmt " [%s:%d]\n",(uint32_t)GetCurrentProcessId(),(uint32_t)GetCurrentThreadId(),##__VA_ARGS__, __FILE__,__LINE__);fflush(log_fp);} while(0)
#else
#include <unistd.h>
#include <sys/syscall.h>
#define gettid(_args...) syscall(__NR_gettid)
#define access(_args...) syscall(__NR_access, ##_args)
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>

#define LOGD(_fmt, _args...)  do {fprintf(stdout, "[D][TXZ][%u][%u] " _fmt " [%s:%d]\n",(uint32_t)getpid(),(uint32_t)gettid(),##_args, __FILE__,__LINE__);fflush(stdout);} while(0)
#define LOGW(_fmt, _args...)  do {fprintf(stdout, "[W][TXZ][%u][%u] " _fmt " [%s:%d]\n",(uint32_t)getpid(),(uint32_t)gettid(),##_args, __FILE__,__LINE__);fflush(stdout);} while(0)
#define LOGE(_fmt, _args...)  do {fprintf(stderr, "[E][TXZ][%u][%u] " _fmt " [%s:%d]\n",(uint32_t)getpid(),(uint32_t)gettid(),##_args, __FILE__,__LINE__);fflush(stderr);} while(0)
#endif // 
  1. 关于函数的定义

c++ 如果跟数据量相关,必须带上 长度的入参以表明 数据量的size,有人说,可以通过 strlen 等方式来进行 获取。实际商因为 strlen 是遇到\0 则表示结尾,而你存入的数据是不定的,也许一串不可见字符。则这个函数本身则会存在问题。

再 c/c++里面 如果不是特殊情况,定义的返回值 成功为0 ,失败则返回 非0

#include <stdio.h>
/**
* 二进制 与字符数据 '\0' 长度等问题
*/
int write(const void* data ,int len){
    if(/**/){// 打开文件失败
        return -1;
    }
    //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    size_t ret_lent = fwrite(data,1,len,fp);
    if(ret_len != len){
        // 写入失败
        return -2;
    }
    //int fclose(FILE *stream);
    int ret_code = fclose(fp);
    if(ret_code != 0){
        return -3;
    }
    return 0;
}
  1. 善于利用 man

因为我记性不好,时长忘记 具体函数签名以及头文件所以 man 真的很有用的

$ man access
$ man fwrite
$ man pthread_create
  1. 字符处理

不可见转可见 ,一般采用base64 的机制

c++版本

std::string base64_decode(std::string const& encoded_string) {
  int in_len = encoded_string.size();
  int i = 0;
  int j = 0;
  int in_ = 0;
  unsigned char char_array_4[4], char_array_3[3];
  std::string ret;
 
  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
    char_array_4[i++] = encoded_string[in_]; in_++;
    if (i ==4) {
      for (i = 0; i <4; i++)
        char_array_4[i] = base64_chars.find(char_array_4[i]);
 
      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
 
      for (i = 0; (i < 3); i++)
        ret += char_array_3[i];
      i = 0;
    }
  }
 
  if (i) {
    for (j = i; j <4; j++)
      char_array_4[j] = 0;
 
    for (j = 0; j <4; j++)
      char_array_4[j] = base64_chars.find(char_array_4[j]);
 
    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
 
    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
  }
  return ret;
}
string bas = base64_decode(ss/*具体的输入内容*/);
cout<<bas<<endl;// 这样就是可见字符了

java版本

Base64.decode(content, Base64.NO_WRAP);// 注意这里的入参,默认是 74个字节 加 `\n` 导致 其他语言解不出来

python

def txz_decode(data):
    strAecEnData = base64.b64decode(data)

不可见转16进制

C版本

int translate(const char* data,size_t dataLen)
    char s_data[256]={0};
    memset(s_data,0,256);
    for(size_t i = 0;i<dataLen;++i)
    {
        //这里不是采用s_data[i*2] 的原因是 clang会对这种写法 报警告的。一个字符转成16进制为2个字节。
        sprintf(&(s_data[i*2]),"%02x",data[i]);
    }
    printf("s_data:%s",s_data);
}

C++

void ByteToHexStr(const unsigned char* source, char* dest, int sourceLen)
{
    short i;
    unsigned char highByte, lowByte;
    for (i = 0; i < sourceLen; i++)
    {
        highByte = source[i] >> 4;
        lowByte = source[i] & 0x0f ;

        highByte = highByte > 9 ? 'a' +(highByte - 10):'0' + highByte;
        lowByte = lowByte > 9 ? 'a' +(lowByte - 10):'0' + lowByte;

         dest[i * 2] = highByte;
         dest[i * 2 + 1] = lowByte;
    }
}
void hex(const string& in, string& out)
{
	char* ptrOutBuf = (char*)malloc(in.size() * 2);
	ByteToHexStr((const unsigned char*)in.c_str(), ptrOutBuf, in.size());
	out.assign(ptrOutBuf, in.size() * 2);
	free(ptrOutBuf);
}

void print_byte_as_hex(const char* buffer, int lenght)
{
	std::string s;
	std::string tmp;
	tmp.assign(buffer, lenght);
	hex(tmp, s);
    ////qDebug("0:%s\n", s.c_str());
}

字符加密

python

def encrypt(key,data):
    dst = ''
    for ch in data[::-1]:
        key+=1
        dst += chr(ord(ch)^(key%256)) # 用异或来处理
    return dst

def decrypt(key,data):
    dst = ''
    for ch in data:
        key+=1
        dst += chr(ord(ch)^(key%256))
    return dst[::-1]
  1. 源码 使用

C版本JSON cJson

网上下载源码进行编译

cJSON* root = cJSON_CreateObject();
cJSON_AddItemToObject(root, "url", cJSON_CreateString(""));
printf("%s",cJSON_PrintUnformatted(root));

c++版本 jsoncpp

::Json::Value resqJson;
resqJson["url"] = "";
cout<<resqJson.toStyledString()<<endl;

以上的都是比较简单的库,一般都是网上找到 相应的 源码,然后采用具体的工具链来编译的, [啥?你想编译windows版本的openssl?呃呃呃]

  1. 条件编译
#if 0
#error 123 // 一般用来判断某个分支是否编译到。非常好用
#endif
#ifdef XXX
#endif
#if  !(defined(XXX) || defined(YYY))
#endif
  1. 分析库

使用到的工具有 nm objdump ar addr2line

nm # 用于分析静态库
objdump # 用于分析动态库
#eg:objdump -Tt xx.so |grep test
ar #打包
#eg: ar -x xx.a # 解压为.o 文件
#eg: ar rc xxx.a *.o # 压缩为 静态库
addr2line #分析指定地址符号
#eg: ./arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line 0029a068 -C -f -e  path
  1. 动态库加密

动态库,也是一种特殊格式的文件,只要了解其文件格式内容,则可以进行深层次的处理。

lib_sec_offset=`${READELF} -SW ${libfile} | grep .text | awk '{print $(NF-7)}'`;

echo $lib_sec_offset
methods = []
for method in sys.argv[3:]:
    methods.append(method)
    methods.append(method + "@@Base")
# 执行 shell命令
cmd = "$OBJDUMP -d -s -j .text " + inputlib
cmd_result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)  # 使用&&来进行连续的操作
content = cmd_result.stdout.read()

regex = re.compile(
    "([\\da-zA-Z]+?) <(.+?)>:\\n( +?[\\da-zA-Z]+?:.+?\\n)* +?([\\da-zA-Z]+?):.+")
result = content.split("\n\n")
for line in result:
    #  注意 match 和search 的区别: match 是从头开始查找 search 是只要找到就算匹配成功
    result = regex.match(line)
    if result:
        if result.group(2) in methods:
            println("0x" + result.group(1) + " \\\n")
            println("0x" + result.group(4) + " \\\n")
  1. 编译参数
-Wl,-Bstatic 表明后面跟随的都按静态库进行查找链接,优先查找静态库

-Wl,-Bdynamic 表明后面跟随的都按动态库进行查找链接,优先查找动态库

-Wl,--whole-archive 表明将后面的静态库中的全部目标文件转变成共享库文件,迫使全部的目标文件进度共享库,全量编译

-Wl,--no-whole-archive 关闭--whole-archive选项对后置的链接库的影响

-Wl,-Bsymbolic 表明优先使用内部符号,一般情况下,链接共享库会覆盖共享库内的定义

-Wl,-z,defs 禁止在目标文件中使用未定义的符号。共享库中仍然允许未定义的符号。

-Wl,--gc-sections 消除未使用的代码和数据的操作

qmake

ENGINE = TEST
DEFINES +=  ?ENGINE #定义宏
contains(DEFINES,TEST){
    HEADERS += engine/test.h
    unix|win32: LIBS += -L?PWD/engine/test/ -llibTest
    INCLUDEPATH += ?PWD/engine/test
    DEPENDPATH += ?PWD/engine/test
}else{
    message('hello else '?ENGINE)
}