Browsed by
标签: c/c++

C/C++ 多动态库标识符冲突

C/C++ 多动态库标识符冲突

场景:
最近在写一个程序的时候,需要调A、B两个服务的客户端动态库,两个客户端动态库中函数有重名(更确切的是重定义)的情况。现象,单写A客户端demo程序运行正常,但集成到程序中死活连不上A服务。


原因:
A、B两个客户端动态库中函数有重定义现象。即都有funcX(int arg)(虽然funcX不是对外接口),但执行过程中A动态库调用了B的funcX,导致程序不正常。而单写A的客户端demo时只用A的动态库,A动态库还是用的A中的funcX,程序正常。

简单来说就是违反了单一定义原则One Definition Rule)。

简单示例说明

fun1.h

#include <stdio.h>
int print1();

fun1.cpp

#include "fun1.h"
int printfun()
{
    printf("1\n");
}
int print1()
{
    printfun();
    return 0;
}

fun2.h

#include <stdio.h>
int print2();

fun2.cpp

#include "fun2.h"
int printfun()
{
    printf("2\n");
}
int print2()
{
    printfun();
    return 0;
}

测试程序test.cpp

#include "fun1.h"
#include "fun2.h"

int main()
{
    print1();
    print2();
    return 0;
}

Makefile

test: libfun1.so libfun2.so
    g++ test.cpp -o test -L ./ -lfun1 -lfun2
libfun1.so:
    g++ fun1.cpp -shared -fpic -o libfun1.so
libfun2.so:
    g++ fun2.cpp -shared -fpic -o libfun2.so
clean:
    rm  -rf libfun1.so libfun2.so test

执行结果

可以看到我们本意是希望一个打印1一个打印2,结果全部打印的1

解决方案1(-Wl,-Bsymbolic )(不推荐)

创建动态链接库时,添加编译选项-Wl,-Bsymbolic,其中Wl表示将紧跟其后的参数,传递给连接器ld。首先看一下-Bsymbolic,    ld的man手册(意思就是优先使用链接库内的定义实现)

-Bsymbolic
When creating a shared library, bind references to global symbols to the definition within the shared library, if any.  Normally, it is possible for a program linked against a shared library to override the definition within the shared library.  This option can also be used with the –export-dynamic option, when creating a position independent executable, to bind references to global symbols to the definition within the executable.  This option is only meaningful on ELF platforms which support shared libraries and position independent executables.

修改Makefile

test: libfun1.so libfun2.so
    g++ test.cpp -o test -L ./ -lfun1 -lfun2
libfun1.so:
    g++ fun1.cpp -shared -fpic -o libfun1.so -Wl,-Bsymbolic
libfun2.so:
    g++ fun2.cpp -shared -fpic -o libfun2.so -Wl,-Bsymbolic
clean:
    rm  -rf libfun1.so libfun2.so test

方案一:执行结果


解决方案2( -fvisibility=hidden )

创建动态库的时,添加编译选项-fvisibility=hidden
首先看一下  -fvisibility=default|internal|hidden|protected,gcc的man手册.

-fvisibility=default|internal|hidden|protected

Set the default ELF image symbol visibility to the specified option—all symbols are marked with this unless overridden within the code. Using this feature can very substantially improve linking and load times of shared object libraries, produce more optimized code, provide near-perfect API export and prevent symbol clashes(防止标志符冲突).  It is strongly recommended that you use this in any shared objects you distribute(强烈建议使用在你写的共享库).

修改头文件
fun1.h

#include <stdio.h>
__attribute__ ((visibility("default"))) int print1();

fun2.h

#include <stdio.h>
__attribute__ ((visibility("default"))) int print2();

修改Makefile

test: libfun1.so libfun2.so
    g++ test.cpp -o test -L ./ -lfun1 -lfun2
libfun1.so:
    g++ fun1.cpp -shared -fpic -o libfun1.so -fvisibility=hidden
libfun2.so:
    g++ fun2.cpp -shared -fpic -o libfun2.so -fvisibility=hidden
clean:
    rm  -rf libfun1.so libfun2.so test

执行结果

解决方案3( 命名空间 )

严格使用命名空间。

fun1.h

#include <stdio.h>
namespace fun1 {
    
int print1();

}

fun1.cpp

#include "fun1.h"
namespace fun1 {
int printfun()
{
    printf("1\n");
}
int print1()
{
    printfun();
    return 0;
}
}

fun2.h

#include <stdio.h>
namespace fun2 {
    
int print2();

}

fun2.cpp

#include "fun2.h"
namespace fun2 {
int printfun()
{
    printf("2\n");
}
int print2()
{
    printfun();
    return 0;
}
}

test.cpp

#include "fun1.h"
#include "fun2.h"

int main()
{
    fun1::print1();
    fun2::print2();
    return 0;
}

Makefile

test: libfun1.so libfun2.so
    g++ test.cpp -o test -L ./ -lfun1 -lfun2
libfun1.so:
    g++ fun1.cpp -shared -fpic -o libfun1.so
libfun2.so:
    g++ fun2.cpp -shared -fpic -o libfun2.so
clean:
    rm  -rf libfun1.so libfun2.so test

执行结果

范例(leveldb)

项目中CMakeLists.txt片段,当编成动态库时添加编译选项-fvisibility=hidden

if(BUILD_SHARED_LIBS)
  # Only export LEVELDB_EXPORT symbols from the shared library.
  add_compile_options(-fvisibility=hidden)
endif(BUILD_SHARED_LIBS)

头文件export.h

// Copyright (c) 2017 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.

#ifndef STORAGE_LEVELDB_INCLUDE_EXPORT_H_
#define STORAGE_LEVELDB_INCLUDE_EXPORT_H_

#if !defined(LEVELDB_EXPORT)

#if defined(LEVELDB_SHARED_LIBRARY)
#if defined(_WIN32)
//Windows 系列
#if defined(LEVELDB_COMPILE_LIBRARY)
#define LEVELDB_EXPORT __declspec(dllexport)
#else
#define LEVELDB_EXPORT __declspec(dllimport)
#endif  // defined(LEVELDB_COMPILE_LIBRARY)

#else  // defined(_WIN32)
//Linux gcc系列
#if defined(LEVELDB_COMPILE_LIBRARY)
#define LEVELDB_EXPORT __attribute__((visibility("default")))
#else
#define LEVELDB_EXPORT
#endif
#endif  // defined(_WIN32)

#else  // defined(LEVELDB_SHARED_LIBRARY)
#define LEVELDB_EXPORT
#endif

#endif  // !defined(LEVELDB_EXPORT)

#endif  // STORAGE_LEVELDB_INCLUDE_EXPORT_H_

头文件db.h片段

...
namespace leveldb {
...
class LEVELDB_EXPORT DB {
...
};
...
}

总结

不推荐使用第一种做法的原因:你是首先用自己定义的标志符,但别人不一定这么做了,有可能其他的动态库没有添加优先使用本动态库标志符的选项。

自己提供动态库时,规范做法应该如leveldb项目,严格使用命名空间,并且使用-fvisibility=hidden,只导出要导出的定义,其他的隐藏。

sprintf 段错误

sprintf 段错误

最近迁移代码测试功能的时候发现一个段错误,打印堆栈显示std::string析构时段错误。最后分析原因:sprintf造成字符数组内存溢出,踩内存,导致附近std::string对象被改修,析构时产生段错误。

原代码

if(response_code == 200)
{
	sprintf(temp,"HTTP请求成功[%s]", strResponce.c_str());
	dispPlayMsg("HTTP请求",index,temp,WEB_LOG);
	
	return TRUE;
}

strResponce为http的响应结果,平时的都是本公司的soap服务,返回
字节数都很少,测试时的服务返回长度超过temp长度,溢出,踩内存。

修改后

if(response_code == 200)
{
	//sprintf易造成越界,导致段错误
	snprintf(temp, sizeof(temp), "HTTP请求成功[%s]", strResponce.c_str());
	dispPlayMsg("HTTP请求",index,temp,WEB_LOG);

	return TRUE;
}

慎用sprintf,尽量使用snprintf。
原来工作中就遇到过sprintf造成的段错误。

leveldb varint 可变长整数

leveldb varint 可变长整数

在读leveldb时看到Varint这个东西,瞬间想起了自己做协议分析、XYHY的时光了。GOOGLE的另一个开源项目protobuf也有varint这个东东。许多软件的私有通讯协议也有用到varint。

1.简介

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。

 

简单来说,可变长的整数,每个字节的最高位标识后续的byte是否还是该数字的一部分

因为所用到的数字(表示长度或者表示其他)往往比较小,用可变长整数信息的表示非常紧凑,自然需要更少的资源。比如(protobuf应用)网络上传输的字节数更少,需要的 IO 更少;(protobuf 或 leveldb)存盘的所用空间更少

Read More Read More