记录C/C++库从32位机器向64为机器移植过程中导致的问题和解决方案

最近程序库从32为向64为机器移植后,在使用过程中出现了一些问题,其中包括上一篇中记录的由于va_list类型不一致导致程序core dump或者出现异常行为的原因,这篇文章记录一下库移植之后出现的小问题。持续更新…

问题一: unsigned long int类型长度不兼容并对其按位操作

一般情况下在32位和64位机器单纯使用该类型在不出现数字大小溢出的情况下一般不会出现问题。但是有一种情况需要特别注意,即当对该unsigned long int进行位操作时,此时的操作结果在32和64位机器的操作结果会不同而导致其他问题。

例如使用unsigned long int类型作为key对其按位进行加密或哈希时,这时的加密或哈希结果会出现问题。

解决方法

将unsigned long int类型换为uint32_t在64位机器上重新编译

问题二: 32位和64位默认对齐不同导致数据类型大小不同

在linux系统下,32位机器数据默认以4字节对齐,64为机器默认以8字节对齐。

看如下结构构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
struct Test{
int a;
char* data[0];
};
int main() {
std::cout << sizeof(Test) << std::endl;
Test t;
std::cout << "&t:" << &t << std::endl
<< "&t.a" << &t.a << std::endl
<< "&t.data:" << &t.data << std::endl;
return 0;
}

32位机器运行结果:

1
2
3
4
8
&t:0x7ffd49f978b0
&t.a0x7ffd49f978b0
&t.data:0x7ffd49f978b4

64位机器运行结果:

1
2
3
4
8
&t:0x7ffd49f978b0
&t.a0x7ffd49f978b0
&t.data:0x7ffd49f978b8

解决方案:

在声明结构体前面加上#pragma pack(1)或#pragma pack(4),强制改变结构体的对齐方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#pragma pack(4)
struct Test{
int a;
char* data[0];
};
int main() {
std::cout << sizeof(Test) << std::endl;
Test t;
std::cout << "&t:" << &t << std::endl
<< "&t.a" << &t.a << std::endl
<< "&t.data:" << &t.data << std::endl;
return 0;
}

Comment and share

C中va_list在32位和64位机器的区别与差异

在将程序从32位机器移植到64位机器的过程中经常出现一些奇奇怪怪的错误,这里记录一下在使用可变参数的过程中导致在32位机器上正常运行的程序移植到64位机器上之后出现段错误的发现过程以及解决方案。

首先看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void parse(va_list ap) {
char* arg;
arg = va_arg(ap, char*);
std::cout << arg << std::endl
<< strlen(arg) << std::endl;
}
void test(const char* format, ...) {
va_list ap;
va_start(ap, format);
for (int i = 0; i < 2; i++) {
parse(ap);
}
va_end(ap);
}
int main() {
test("hget %s %s", "abc", "123456");
}

32位机器的运行结果如下:

1
2
3
4
abc
3
abc
3

64位机器运行结果如下:

1
2
3
4
abc
3
123456
6

原因分析

出现上述结果的原因是由于va_list类型在32位和64位机器的类型不同导致的.

32位va_list

在32位上,va_list的定义为:

1
2
//注意,由于中间宏过多,这里省去了中间如_VA_LIST宏,直接给出实际定义。
typedef va_list char**;

64位va_list

在64位上va_list定义为一个结构体数组,并且数组中记录了可变参数被读的偏移量:

1
2
3
4
5
6
7
// Figure 3.34
typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
void *overflow_arg_area;
void *reg_save_area;
} va_list[1];

程序异常分析

当在32位机器上将va_list(char**)作为参数传递给函数的时候,该函数将从头开始读取该变长参数,还是使用va_list完毕并不记录当前va_list被读的偏移量,所以当第二次传入该va_list还是从头开始读取。

当在64为机器上将va_list(struct 数组)作为参数传递给函数的时候,该函数读取va_list完毕之后,将读取的偏移量记录在结构体中,由于其为数组传入函数,所以该被调用的函数改变了传入的va_list的偏移量。导致下次调用该函数从记录的偏移量开始读,造成不可预测或者内存越界等问题。

移植解决方案

将va_list初始化写到for循环内部,每次调用函数前都初始化va_list即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void parse(va_list ap) {
char* arg;
arg = va_arg(ap, char*);
std::cout << arg << std::endl
<< strlen(arg) << std::endl;
}
void test(const char* format, ...) {
for (int i = 0; i < 2; i++) {
va_list ap;
va_start(ap, format);
parse(ap);
va_end(ap);
}
}
int main() {
test("hget %s %s", "abc", "123456");
}

参考:
https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure
http://blog.csdn.net/doubleface999/article/details/55798710

Comment and share

C中snprintf与vsnprintf函数

虽然snprintf函数在开发过程中比较常用,但是其中有一些细节性的问题需要注意。因为snprintf函数不是C中的标准函数,不同的编译器可能对该函数的实现不同,本文说明是基于GCC编译器。

snprintf函数

函数原型

snprintf函数的作用是将格式化的数据写入字符串。其函数原型如下:

1
snprintf(char* buffer, int n, char* format, ...);

参数说明

  • buffer : 存储格式化字符串的buffer
  • n : 指定格式化字符串的大小,包括\0
  • format : 指定需要格式化字符串的format
  • … : 可变参数

返回值

该函数的返回值为其期望字符串的长度,而不是实际存入buffer中的字符串的长度。且不包括\0

注意点

特别注意函数原型中的第二个参数包括\0的大小,而返回值为期望大小且不包括\0。

例:

1
2
3
4
5
6
7
8
char buffer[256];
//返回值ret = 13,buffer中的内容为123456789
int ret = snprintf(buffer, 10, "%s", "1234567890abc");
memset(buffer, 0x0, sizeof(buffer));
//返回值ret = 3,buffer中的内容为123
ret =snprintf(buffer, 10, "%s", "123");

snprintf与vsnprintf

snprintf和vsnprintf都是C语言printf家族函数的成员。实际上,snprintf和vsnprintf功能完全一样,只是vsnprintf将snprintf中的可变参数换成了av_list类型。如下:

1
2
3
4
5
#include <stdio.h>
int printf(const char* format, ...); //输出到标准输出
int fprintf(FILE* stream, const char* format, ...); //输出到文件
int sprintf(char* buffer, const char* format, ...); //输出到字符串
int snprintf(char* buffer, int n, const char* format, ...); //输出到字符串
1
2
3
4
5
#include <stdarg.h>
int vprintf(const char* format, va_list ap); //输出到标准输出
int vfprintf(FILE* stream, const char* format, va_list ap); //输出到文件
int vsprintf(char* buffer, const char* format, va_list ap); //输出到字符串
int vsnprintf(char* buffer, int n, const char* format, va_list ap); //输出到字符串

va_list获取

可变参数va_list获取方式通过下列函数获取,并且总是成对调用

1
2
va_start(va_list ap, last);
va_end(va_list ap);

简单的使用vsnprintf函数实现snprintf

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdarg.h>
int my_snprintf(char* buffer, int size, const char* format, ...) {
va_list ap;
va_start(ap, format);
int ret = vsnprintf(buffer, size, format, ap);
va_end(ap);
return ret;
}

Comment and share

C++函数指针学习

使用函数指针的优点

使用函数指针有助于我们设计出更优秀,更简洁更高效的程序。在下面的情景中我们常用到函数指针:

  • 使用函数指针作为参数
  • 使用函数指针作为返回值
  • 使用函数指针作为回调函数
  • 使用函数指针数组
  • 类的静态方法和非静态方法的函数指针
  • 使用函数指针实现动态绑定
  • 在结构体中定义函数

使用函数指针提高函数的效率

当通过switch case多用多个相同类型的函数的时候,这个时候使用函数指针可以大大简化函数代码并可以明显的提高程序的执行效率。例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <time.h>
#include <string>
#include <sys/time.h>
using namespace std;
#define StartTime(id) struct timeval __now1105##id;\
gettimeofday(&__now1105##id, 0);
#define EndTime(id) struct timeval __now21105##id; \
gettimeofday(&__now21105##id, 0); \
printf("timer_%s spend time:%d us\n",#id,(__now21105##id.tv_sec-__now1105##id.tv_sec)* 1000000 + (__now21105##id.tv_usec-__now1105##id.tv_usec));
double add(double a, double b) {
return a + b;
}
double sub(double a, double b) {
return a - b;
}
double multi(double a, double b) {
return a * b;
}
double div(double a, double b) {
return a/b;
}
typedef double (*op)(double, double);
void func1(double a, double b, int flag) {
switch(flag) {
case 0:
add(a, b);
break;
case 1:
sub(a, b);
break;
case 2:
multi(a, b);
break;
case 3:
div(a, b);
break;
default:
break;
}
}
//使用函数指针调用函数
void func2(double a, double b, op cb) {
cb(a, b);
}
int main() {
StartTime(func1);
for (int i = 0; i < 100000; i++) {
func1(0.2, 0.034, 3);
}
EndTime(func1);
StartTime(func2);
for (int i = 0; i < 100000; i++) {
func2(0.2, 0.034, multi);
}
EndTime(func2);
}

由上面可以看出,由于上述switch,case中调用的函数类型(返回值类型,参数个数以及对应的类型)完全一致,我们将函数指针以参数的形式传到处理函数中。
运行上述函数的结果如下:

1
2
timer_func1 spend time:2861 us
timer_func2 spend time:2178 us

可以看出使用函数指针的效率远远高于switch case

函数指针用做回调函数

来源于wiki
在计算机程序设计中,回调函数是指通过函数参数传递到其他代码的,某一块可执行代码的引用。这一设计允许底层代码调用在高层定义的子程序。如Linux C中的signal函数就是这样一个例子。

signal底层的其中一个实现版本如下:由其实现可以看出信号处理的回调函数的主要功能是将处理信号的函数指针替换为用户高层自定义的函数地址fun,从而达到当接收到该信号时底层代码调用高层定义代码的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sigfunc *
signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
} else {
act.sa_flags |= SA_RESTART;
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}

当我们使用signal函数的时候,如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <signal.h>
#include <stdio.h>
void signalcb(int signum) {
printf("this is the signal:%d", signum);
}
int main() {
signal(SIGINT, signalcb);
pause();
return 0;
}

函数指针的本质及声明方法以及赋值

函数指针的本质

函数指针类似于数据指针,其本质实质上是一类函数(返回值类型相同,参数个数以及对应的参数类型相同)的入口地址,即该可执行代码在内存中的起始地址。函数指针可以让我们通过函数地址去调用该函数。有利于实现函数的动态绑定,即在运行时才决定去调用哪个函数。

函数指针的声明方法

函数指针的声明方法有两种,包括:

  • 普通函数的函数指针和类静态成员函数的函数指针
  • 类非静态成员函数的函数指针

普通函数的函数指针和类静态成员函数的函数指针:
int (funptr)(int qa, int qb);
类非静态成员函数的函数指针:
int MyClass::(
funptr)(int qa, int qb);

注意上述函数指针声明时(*funptr)中的括号不能省略,若省略,有可能会产生歧义,其意义就变成了返回值为int*的函数定义了

上述两种函数指针声明不兼容的原因如下:(《深度探索C++对象模型》)
获取类的静态和非静态函数的函数指针的地址都是其在内存中实际的地址。那为什么非静态成员的指针需要绑定(指明类)?原因是类的非静态成员需要操作类的数据成员,所以类的非静态成员需要绑定this指针找到类的数据成员。故对nonstatic函数取地址是不完整的。

函数指针的赋值与使用

函数指针赋值

对于普通指针和类的静态成员指针,有两种赋值方式:

1
2
funptr = fnc1
funptr = &fun1

对于类的非静态成员指针,只能用上面第二种形式赋值。为了保证形式的一致性和避免二义性,一般统一使用取地址符号进行赋值可避免错误出现。

1
funptr = &classInstance.func();

函数指针使用

函数指针的使用类似于赋值。
对于普通指针和类的静态成员函数指针的调用,有两种调用方式:

1
2
3
funptr(1,2);
(*funptr)(1,2);

对于类的静态成员函数,只能用上述第二种方式进行调用,为了保持一致性和避免二义性,一般统一使用使用*来解引用函数指针进行调用.

函数指针作为参数

函数指针是一个类型,将函数指针作为参数传入函数中与其他参数类似。使用方式与上述函数指针用做回调函数中signal接收参数的方式相同。这里不再详解

函数指针作为返回值

既然函数指针是函数的入口地址,所以函数指针也可以作为函数的返回值返回。不过函数指针作为函数的返回值的写反比较复杂。
若一个函数func只有一个参数int, 其返回值类型是float (*) (float, float);则其函数原型如下:

1
float (*func(int op)) (float, float);

函数指针作为函数返回值的原则是,将函数名以及参数写到*后,函数指针的返回值放在最前面,函数指针的参数放在最后面。

由上面的知识我们可以分析一下signal函数原型。

1
void (*signal)(int signo,void (*func)(int)))(int);

其定义了一个有两个参数分别为int和void (func)(int),返回值为void (func)(int)的函数。

其本质上是将函数处理指针替换为用户自定义的函数指针。那为什么需要返回值为void (func)(int)呢?原因是*signal函数的返回值是旧的信号处理函数的指针,我们可以通过这个指针暂时改变signal函数处理信号的方式。之后可以通过返回的指针恢复该信号默认的处理方式。

注意:signal的信号处理其函数中的int参数的含义是:当信号到达的时候,内核将该信号以整形的方式传给处理器函数,即为void (*func)(int sig)中的sig.

使用typedef定义函数指针

一般情况下,如果通过普通的方式定义函数指针,在使用的很不方便。这个时候我们可以通过typedef定义函数指针的新类型。通过typedef定义新类型时与普通类型定义新类型方式不同。对于普通类型,定义方式如下:

1
typedef int64_t int64;

但是对于函数指针,定义方式如下:

1
2
3
4
5
6
7
//此处定义个名字为func的类型,它表示函数指针float (*func)(float, float)类型
typedef float (*func)(float, float);
float function(float a, floatb);
//使用func定义变量
func test = &function;

Comment and share

C++中find_if查找vector中的特定struct以及值域查询

由于struct不是C++中的内置类型所以不能用std::find直接查找,而且find函数不能满足值域的查询。这个时候需要使用find_if来进行查询。

find_if函数

find_if是一个模板函数,函数原型及函数定义:

1
2
3
4
5
template <class InputIterator, class Predicate>
InputIterator find_if(InputIterator first, InputIterator end, Predicate pred) {
while (first != last && !pred(*first)) ++first;
return first;
}

函数参数

  • first : 起始迭代器
  • end : 结束迭代器
  • pred : 用于比较数值的函数或者函数对象(仿函数)。遍历条件即为pred()为真.

函数返回值

若有满足pred条件的元素,返回该元素的迭代器,否则返回end.

函数说明

该函数最重要的环节是pred,它的核心环节是重载()运算符,因为每个容器迭代器的*运算符得到的结果都是该容器的value_type的值,所以改重载函数的参数类型是value_type的引用类型

find_if函数应用

在struct的vector中查找特定的对象.特别注意的是:仿函数的参数类型是值的const引用,但是finder的构造参数是实际要比较的值的类型,在使用过程中,向构造函数中传的值是要比较的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
struct book{
int m_iID;
string m_strName;
book(int t_id, string t_name):m_iID(t_id), m_strName(t_name) {}
};
struct book_finder{
int m_iID;
book_finder(int t_id):m_iID(t_id) {}
bool operator() (const book& t) {return t.m_iID == m_iID;}
};
int main() {
vector<book> bookVc;
book book1(0, "书0");
book book2(1, "书1");
book book3(2, "书2");
book book4(3, "书3");
bookVc.push_back(book1);
bookVc.push_back(book2);
bookVc.push_back(book3);
bookVc.push_back(book4);
book target(1, "书");
if (std::find_if(bookVc.begin(), bookVc.end(), book_finder(target.m_iID)) != bookVc.end()) {
cout << "存在1" << std::endl;
} else {
cout << "不存在1" << std::endl;
}
target.m_iID = 10;
if (std::find_if(bookVc.begin(), bookVc.end(), book_finder(target.m_iID)) != bookVc.end()) {
cout << "存在10" << std::endl;
} else {
cout << "不存在10" << std::endl;
}
}

Comment and share

C语言宏定义相关

C语言宏定义在代码编写中很常见,它常会带来一些很高的性能和很方便的写法,在看Linux源码中sockaddr_in的时候遇到宏定义中##。特地在此记录.

宏定义中##用法

问题背景

Linux中sockaddr_in的定义在文件/netinet/in.h文件中。具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; // Port number.
struct in_addr sin_addr; // Internet address.
// Pad to size of struct sockaddr.
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
#define __SOCKADDR_COMMON(sa_prefix)\
sa_family_t sa_prefix##family

从上面可以看出,__SOCKADDR_COMMON的宏定义中出现了##的使用方法。那它在宏定义中的意思是什么呢?

##详解

##是一种分隔连接方式。它的作用是先分隔,然后进行强制连接。

在普通的宏定义中,预处理器一般吧空格解释为分段标志,然后进行相应的替换工作。但是这样做的结果是被替换的段之间会出现空格。如果我们不希望这些空格出现,可以使用##来代替空格。

如:

1
2
#define type1(type,name) type name_##type##_type
#define type2(type,name) type name##_##type##_type

上述type1(int,c)将被替换为:int name_int_type
上述type2(int,c)将被替换为:int c_int_type

故我们再回去看SOCKADDR_COMMON的宏定义. SOCKADDRCOMMON (sin);将被解释为sa_family_t sin_family;

注意在函数参数中使用##可以将空字符串过滤掉,否则会出现问题。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define comac_get_args_cnt( ... ) comac_arg_n( __VA_ARGS__ )
#define comac_arg_n( _0,_1,_2,_3,_4,_5,_6,_7,N,...) N
#define comac_args_seqs() 7,6,5,4,3,2,1,0
#define comac_join_1( x,y ) x##y
//计算可变参数...中参数的个数.注意由于seq的范围为7-0,该宏可识别的参数个数的范围为0-7个。超过7个参数识别有问题
#define comac_argc( ... ) comac_get_args_cnt( 0,##__VA_ARGS__,comac_args_seqs() )
int main() {
//若上述comac_get_cnt中的__VA_ARGS__去掉##,结果为1,加上为0。
comac_argc();
}

#详解

在宏定义中,#符号的作用是将宏定义参数用””括起来,

1
2
#define example(test) printf("%s", #test);
example(123 456); //printf("%s", "123 456");

宏定义中占位符理解

在宏定义中我们可以使用字母或者单词作为占位符,初次之外,我们也可以使用_1,_2作为占位符,不要被它蒙骗了,其作用与单词作为占位符相同。如:

1
2
#define sum(a,b) (a+b)
#define sum(_1,_2) (_1+_2)//作用同上

Comment and share

CprintNULL

in C++ problems

printf %s情况下字符串为NULL的输出结果

遇到问题

1
2
printf("this%s\n", s);
printf("%s\n", s);

当s为NULL的时候,执行上面三个语句两个语句执行结果分别为:

1
2
this(null)
segment fault

问题分析

在ANSI C中没有对printf时字符串为NULL的情况进行明确的定义,所以当出现这种情况时往往是未定义行为。
所以上面第一种情况会出现segment fault的情况是一种未定义行为,可能在其他编译器上不会出现段错误。

上面出现段错误的行为我们可以通过反汇编进行查看。

反汇编的结果如下:

1
2
3
4
5
804842d: c7 04 24 20 85 04 08 movl $0x8048520,(%esp)
8048434: e8 0b ff ff ff call 8048344 <printf@plt>
8048439: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048440: e8 df fe ff ff call 8048324 <puts@plt>
8048445: 83 c4 14 add $0x14,%esp

从上面可以看出第一个执行被汇编成真正的printf指令,而第二个简单的printf NULL的指令被汇编成puts。
所以才出现了上面的结果。

Comment and share

why assigment operator can not be frined

this is a problem in my work and I have find th solution on stackoverflow,so recorder here

problem description

When I refactor my object, I have a problem which need to change the return value
of and function std::string to a struct data, but I don’t want to change my code
where the function be used, so I want to overload the assignment operator which
will assign a struct to string.The code is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <string>
class data_struct{
friend std::string operator = (std::string& s, data_struct& d);
private:
bool success{false};
std::string message{""};
public:
data_struct(bool t_success, std::string t_message):
success(t_success), message(t_message) {}
}
std::string operator = (std::string& s, data_struct& d) {
s = d.s;
return s;
}
int main() {
data_struct d(false,"haha");
std::string s = d;
}

when I compile this file , this is an error follows:

1
2
3
4
5
6
7
8
9
main.cpp:3:64: error: ‘std::__cxx11::string operator=(std::__cxx11::string&, data_struct&)’ must be a nonstatic member function
friend std::string operator = (std::string& s, data_struct& d);
^
main.cpp:12:13: error: expected initializer before ‘operator’
std::string operator = (std::string& s, data_struct& d) {
^
main.cpp: In function ‘int main()’:
main.cpp:19:19: error: conversion from ‘data_struct’ to non-scalar type ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’ requested
std::string s = d;

why does this happen?

Firstly, it should be noted that this has nothing to do with the operator being implemented as a friend specifically. It is really about implementing the copy-assignment as a member function or as a non-member (standalone) function. Whether that standalone function is going to be a friend or not is completely irrelevant: it might be, it might not be, depending on what it wants to access inside the class.

Now, the answer to this question is given in D&E book (The Design and Evolution of C++). The reason for this is that the compiler always declares/defines a member copy-assignment operator for the class (if you don’t declare your own member copy-assignment operator).

If the language also allowed declaring copy-assignment operator as a standalone (non-member) function, you could end up with the following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Class definition
class SomeClass {
// No copy-assignment operator declared here
// so the compiler declares its own implicitly
...
};
SomeClass a, b;
void foo() {
a = b;
// The code here will use the compiler-declared copy-assignment for `SomeClass`
// because it doesn't know anything about any other copy-assignment operators
}
// Your standalone assignment operator
SomeClass& operator =(SomeClass& lhs, const SomeClass& rhs);
void bar() {
a = b;
// The code here will use your standalone copy-assigment for `SomeClass`
// and not the compiler-declared one
}

As seen in the above example, the semantics of the copy-assignment would change in the middle of the translation unit - before the declaration of your standalone operator the compiler’s version is used. After the declaration your version is used. The behavior of the program will change depending on where you put the declaration of your standalone copy-assignment operator.

This was considered unacceptably dangerous (and it is), so C++ doesn’t allow copy-assignment operator to be declared as a standalone function.

It is true that in your particular example, which happens to use a friend function specifically, the operator is declared very early, inside the class definition (since that’s how friends are declared). So, in your case the compiler will, of course, know about the existence of your operator right away. However, from the point of view of C++ language the general issue is not related to friend functions in any way. From the point of view of C++ language it is about member functions vs. non-member functions, and non-member overloading of copy-assignment is just prohibited entirely for the reasons described above.

Solution

due to the solution above is not proper.So I overWrite the orignal function, and
invoke different version in their needed place.

Comment and share

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China