一些篇幅短小待深挖的知识随笔罗列在这里。


UE运行时动态更新寻路

Windows访问共享提示没有权限使用网络资源

如果Windows在访问共享的文件夹时提示:

无法访问。你可能没有权限使用网络资源。情欲这台服务器的管理员联系以查明你是否有访问权限。

解决办法:添加Windows凭证。
控制面板显示所有控制面板项,搜索凭据管理器,进入后点击Windows凭据,然后添加一个*Windows凭据
根据需要访问的共享的IP和账户信息创建即可。

UE EditorModule在Standalone下不会加载

注意:Module的Type如果为Editor则在Standalone下是不加载的。会出现在编辑器模式下是正常的,但是在Standalone运行时没有任何效果,新建的插件最好以Developer或者Runtime。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "TaskTools",
"Description": "",
"Category": "Other",
"CreatedBy": "",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"Installed": false,
"Modules": [
{
"Name": "TaskTools",
"Type": "Developer",
"LoadingPhase": "Default"
}
]
}

UE版本号的宏定义

引擎中标记引擎版本的宏:

  • ENGINE_MAJOR_VERSION
  • ENGINE_MINOR_VERSION
  • ENGINE_PATCH_VERSION

等,是在Engine/Source/Runtime/Launch/Resources/Version.h中定义:

1
2
3
4
5
// These numbers define the banner UE4 version, and are the most significant numbers when ordering two engine versions (that is, a 4.12.* version is always
// newer than a 4.11.* version, regardless of the changelist that it was built with)
#define ENGINE_MAJOR_VERSION 4
#define ENGINE_MINOR_VERSION 19
#define ENGINE_PATCH_VERSION 0

UE蓝图的循环迭代最大计数

UE蓝图里面的循环上限是需要在ProjectSetting-Engine-Blueprints里设置(默认是1000000):

UE Actor Replication Config

Replication

OnlyRelecantToServer(default false)
if true,this actor is only relevant its owner,if this flag is changed during play,all non-owner channels would need to be explicitly close.

Always Relevant(default false)

Always relevant for network(overrides bOnlyRelevantToOwner)

ReplicateMovement(default true)

if true,replicate movement/location related properties.
Actor must also be set to replicate.

  1. see SetReplicates()
  2. see https://doc.unrealengine.com/latest/INT/Gameplay/Networking/Replication/

NetLoadOnClient(default true)

This actor will be loaded on network clients during map load.

NetUseOwnerReplovancy(default false)

If actor has valid Owner, call Owner's IsNetRelevantFor and GetNetPriovity

Replicates(default true)

if true,this actor will replicate to remote machines
see SetReplicates()

NetDormancy(default DORM_Awake)

Dormancy setting for actor ro take itself off the replication list without being destory on clients.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// This actor can go dormant,but is not currently dormant,Game code will tell it when it go dormant.
enum ENetDormancy
{
DORM_Never,
DORM_Awake,
DORM_DormantAll,
DORM_DormantPartial,
DORM_Initial,
DORM_MAX,
}
DORM_Never: This actor can never go network dormant.
DORM_Awake: This actor can go dormant, but is not currently dormant. Game code will tell it when it go dormant.
DORM_DormantAll: This actor wants to go fully dormant for all connections.
DORM_DormantPartial: This actor may want to go dormant for some connections, GetNetDormancy() will be called to find out which.
DORM_Initial: This actor is initially dormant for all connection if it was placed in map.
DORM_MAX

NeuCullDistanceSquared(default 225000000.0)

Suqare of the max distance from the client's viewpoint that this actor is relevant and will be replicated.

NetUpdateFrequency(default 100.0)

how often(per second) this actor will be considered for replication,used to determine NetUpdateTime.

MinNetUpdateFrequency(default 2.0)

Used to determine what rate to throttle down to when replicated properties are changing infrequently.

NetPriority: (default 3.0)

Priority for this actor when checking for replication in a low bandwidth or saturated siuation,higher priority means it is more likely to replicate.

Replicated Movement

LocationQuantization Level: (default EVectorQuantization::RoundTwoDecimals)

1
2
3
4
5
6
7
8
9
enum EVectorQuantization
{
RoundWholeNumber,
RoundOneDecimal,
RoundTwoDecimals,
}
RoundWholeNumber: Each vector component will be rounded to the nearest whole number.
RoundOneDecimal: Each vector component will be rounded, preserving one decimal place.
RoundTwoDecimals: Each vector component will be rounded, preserving two decimal places.

VelocityQuantization Level: (default ERotatorQuantization::RoundWholeNumber)

1
2
3
4
5
6
7
enum ERotatorQuantization
{
ByteComponents,
ShortComponents,
}
ByteComponents: The rotator will be compressed to 8 bits per component.
ShortComponents: The rotator will be compressed to 16 bits per component.

RotationQuantization Level(default ByteComponents)

Allow turing the compression level for replicated rotation.You should only need to change this from the default if you see visual artfacts.

1
2
3
4
5
6
7
enum ERotatorQuantization
{
ByteComponents,
ShortComponents,
}
ByteComponents: The rotator will be compressed to 8 bits per component.
ShortComponents: The rotator will be compressed to 16 bits per component.

UE Package Error: noexcept

如果打包时遇到下列错误:

1
C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc

*.target.cs中添加:

1
bForceEnableExceptions = true;

即可。

UE Package:Use Log in Shipping

在我之前的文章Macro defined by UBT in UE4#USE_LOGGING_IN_SHIPPING中写道,可以在Module的*.target.cs中使用bUseLoggingInShipping来控制打包模式为Shipping的包是否启用日志。
注意:目前只支持源码版引擎,如果在Launcher安装的Engine会报链接错误:

1
error LNK2001: unresolved external symbol "struct FLogCategoryLogSerialization LogSerialization" (?LogSerialization@@3UFLogCategoryLogSerialization@@A)

同时,如果出现下列错误:

1
C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc

则在*.target.cs中添加:

1
bForceEnableExceptions = true;

即可。

UE Log macro defined

UE源码中的一些Log定义:
EngineLogs.h中定义了这些Log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogPath, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogPhysics, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogBlueprint, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogBlueprintUserMessages, Log, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogAnimation, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogRootMotion, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogLevel, Log, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogSkeletalMesh, Log, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogStaticMesh, Log, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogNet, Log, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogRep, Log, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogNetPlayerMovement, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogNetTraffic, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogRepTraffic, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogNetFastTArray, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogNetDormancy, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogSkeletalControl, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogSubtitle, Log, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogTexture, Log, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogPlayerManagement, Error, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogSecurity, Warning, All);
ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogEngineSessionManager, Log, All);

CoreGlobals.h中定义了下面这些Log:

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
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogHAL, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogMac, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogLinux, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogIOS, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogAndroid, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogPS4, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogXboxOne, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogWindows, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogSwitch, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogQuail, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogSerialization, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogUnrealMath, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogUnrealMatrix, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogContentComparisonCommandlet, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogNetPackageMap, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogNetSerialization, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogMemory, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogProfilingDebugging, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogCore, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogOutputDevice, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogSHA, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogStats, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogStreaming, Display, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogInit, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogExit, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogExec, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogScript, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogLocalization, Error, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogLongPackageNames, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogProcess, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogLoad, Log, All);
// Temporary log category, generally you should not check things in that use this
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogTemp, Log, All);

最近用到的一些Log定义地方一并贴出:

1
2
3
// Module: Json
// File: Runtime/Json/Public/JsonGlobals.h
JSON_API DECLARE_LOG_CATEGORY_EXTERN(LogJson, Log, All);

正则匹配C函数声明

1
^([\w\*]+( )*?){2,}\(([^!@#$+%^;]+?)\)(?!\s*;)

来源:Regex to pull out C function prototype declarations?

UE蓝图的Paste工具

可以把蓝图的代码复制到blueprintue.com,把网址发给别人,就能够让别人看到蓝图里的节点,就像在UE的编辑器里一样。

UE创建StandaloneApplication

使用UE也可以写应用程序(StandaloneApplication: Target is Program),可以把UE当作一个超大的ThridParty.
我写了一个模板:hxhb/UEProgramTemplate.
运行效果:

但是想要创建新的StandaloneApplication太麻烦了,我写了一个小工具可以直接创建:hxhb/UECreateProgramTemplateTool.

1
2
3
# Usage
$ create_program.exe $ProgramName
Create Standalone Program Successed!
  1. move $ProgramName Folder to Engine\Source\Programs (version of source code)
  2. run GenerateProgramProject.bat
  3. OpenProgramProject.bat

用起来相当酸爽。

注意
*.target.cs中的配置会影响UBT生成的一些宏定义,比如:

  • bBuildWithEditorOnlyData控制的是WITH_EDITORONLY_DATA
  • bCompileAgainstEngine控制的是WITH_ENGINE

开发Program类型的程序时需要注意这些配置。可以在UnrealBuildSystem/Targets查看*.target.cs支持的全部参数。
UBT的代码里解析*.Target.cs里的配置在Programs\UnrealBuildTools\Configuration\URBuildTarget.csSetupGlobalEnvironment函数。

C语言中聚合结构的初始化

C语言的聚合结构还有这种方式的初始化:

1
2
struct { int a;float b; } x ={ .a = 2,.b = 2.2 };
struct { int a[3], b; } w[] ={ [1].a[0] = 2 };

这是因为inielizer的designator可以是[constant-expression].identifier.
PS:sizeof(w) == ?是个有趣的问题。
详情请看[ISO/IEC 9899:1999 §6.7.8].

通过函数指针进行函数调用不可以使用默认参数

如题:

1
2
3
4
5
6
7
8
9
10
void func(int ival=123)
{
printf("%d\n",ival);
}
int main()
{
void(*fp)(int)=func;
fp(); // error: too few arguments to function call, expected 1, have 0
}

先来看一下函数的默认参数是在何时被填充上去的,老样子还是看LLVM-IR代码:

1
2
3
4
5
6
7
8
void func(int ival=123)
{
printf("%d\n",ival);
}
int main()
{
func();
}

其main函数的LLVM-IR代码为:

1
2
3
4
define dso_local i32 @main() #6 {
call void @_Z4funci(i32 123)
ret i32 0
}

可以看到在编译时通过代码分析直接把省略掉的参数用默认值给补上了。
函数指针只具有函数的地址值,不包含任何的实参信息,也就不能在函数指针的访问里使用默认参数咯。
注:成员函数指针也同理。

unspecified behavior

behavior, for a well-formed program construct and correct data, that depends on the implementation.[ Note: The implementation is not required to document which behavior occurs. The range of possible behaviors is usually delineated by this International Standard. — end note ]

well-formed program

[ISO/IEC 14882:2014 §1.3.26]C++ program constructed according to the syntax rules, diagnosable semantic rules, and the One Definition
Rule (3.2).

Implementation-Define(实现定义行为)

[ISO/IEC 14882:2014]behavior, for a well-formed program construct and correct data, that depends on the implementation and that each implementation documents

UB(未定义行为)

[ISO/IEC 14882:2014 §1.9.4]This International Standard imposes no requirements on the behavior of programs that contain undefined behavior.
[ISO/IEC 14882:2014 §1.3.24]behavior for which this International Standard imposes no requirements [ Note: Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. — end note ]

这意味着具有UB的程序的行为不可预测,什么情况都有可能发生。

解决Windows在英文语言下中文字体太丑的问题

因为在中文语言下我替换了雅黑的字体,看起来不那么虚,但是当我把系统语言切换为English后发现中文的字体都很虚啊。
查了一些如何在英文语言下替换中文字体的方法,记录一下。

在英文版Windows中默认字体一般都是Segoe UI、Tahoma、Microsoft Sans Serif之类,这些字体都是没有中文字库的,所以当需要显示中文字体的时候,就会出现各种难看的文字效果。
解决方法是使用“字体链接(FontLink)”技术:就是在使用英文字体时,如果遇到这种字体中没有的字符,就会到注册表相应的位置去找它链接的字体。
英文版Win7的默认字体是Segoe UI,这是个纯英文字体。所以要显示中文的时候它就会去找它链接的字体。
打开注册表地址:

1
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink

这里面就是各个字体的链接字体的信息。
在这个键值下会发现Segoe UI,Tahoma这些常用英文字体的链接,查看Segoe UI的值,它的第一行为Tahoma.TTF,意思就是遇到中文时,它默认到Tahoma.TTF文件里去找,如果没有发现这个字,再到第二行的字体里去找。
双击编辑Segoe UI的项,可以看到雅黑的链接顺序在中间的位置,我们要做的就是把雅黑的顺序提到最前面:

替换后:

然后重启即可。

以防原链接失效,上面的部分内容摘录自后面的文章,作为本站的备份用。想要了解更具体内容可看这篇文章:解决英文版Windows中中文字体难看的问题

关于注入类名字(injected-class-name)的问题

首先要先了解一下什么叫做注入类名字(injected-class-name):

[ISO/IEC 14882:2014(E) §9.0.2]The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name is treated as if it were a public member name.

意思就是类的名字被嵌入到类的作用域中,为了访问检查的目的,注入类名称被视为public成员(注意这一句)。
其实对注入类名字的声明类似于下面这样:

1
2
3
4
5
6
7
8
9
10
class A {
public:
// 模仿注入类名字
using AType=A;
};
class B : private A { };
class C : public B {
// A::AType aobj;
AType* aobj;
};

在类内的名字查找是先从当前作用域开始的,注入类名字被在继承层次中看的更明显一些:

1
2
3
4
5
6
class A{};
class B:public A{
// 等价于A aobj;
// 但与::A aobj;有区别
B::A aobj;
};

如上面的代码所示,可以使用B::A来限定B继承层次的类型A.
上面描述的内容中写到,注入类名字被视为public成员,但是如果我们在继承层次中把基类标记为了private,会怎样?

1
2
3
4
5
6
7
8
9
10
11
class A { };
class B : private A { };
class C : public B {
A* p0;
};
int main(int argc,char* argv[])
{
C cobj;
return 0;
}

编译一下代码看一下:

1
2
3
4
5
6
7
8
9
10
injected-class-name.cpp:4:3: error: 'A' is a private member of 'A'
A* p0;
^
injected-class-name.cpp:2:11: note: constrained by private inheritance here
class B : private A { };
^~~~~~~~~
injected-class-name.cpp:1:7: note: member is declared here
class A { };
^
1 error generated.

那我们使用B::A或者C::A呢?同样也会具有一样的错误。

因为在派生类中对基类名字的名字查找(name lookup)找到的是注入类名字(injected-class-name):

[ISO/IEC 14882:2014(E) §11.1.5]In a derived class, the lookup of a base class name will find the injected-class-name instead of the name of the base class in the scope in which it was declared.

要解决这样的问题,要限定名字空间(上面的例子要改为::A):

1
2
3
4
5
6
7
8
9
10
11
class A { };
class B : private A { };
class C : public B {
::A* p0;
};
int main(int argc,char* argv[])
{
C cobj;
return 0;
}

注意,在namespace的scope内一定要注意默认的名字查找(name-lookup)是有namespace限定的。

一个要抠字眼的C++问题

问:派生类的对象对他的基类成员中()是可以访问的?
A) 公有继承的公有成员
B) 公有继承的私有成员
C) 公有继承的保护成员
D) 私有继承的公有成员

乍一看,继承层次下的派生类使用public/protected/private都可以访问基类的public/private成员啊,貌似ACD都对。
但是,注意问题里写的是对象,对象只能够访问类的public成员,所以我在问题里加粗了:)。

reintrtpret_cast的转换(位模式的转换)

指针到整型的转换

指针能够显式转换为任何足够容纳它的整型,但是映射函数是实现定义(implementation-define)的。
类型std::nullptr_t的值能够转换到整型,该转换与转换(void*)0到整型具有相同的意义与合法性。
reinterpret_cast不能用在转换任何类型的值到std::nullptr_t.

[ISO/IEC 14882:2014 §5.2.10.4]A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is implementation-defined. [ Note: It is intended to be unsurprising to those who know the addressing structure of the underlying machine. — end note ] A value of type std::nullptr_t can be converted to an integral type; the conversion has the same meaning and validity as a conversion of (void*)0 to the integral type. [ Note: A reinterpret_cast cannot be used to convert a value of any type to the type std::nullptr_t. — end note ]

整型到指针的转换

整型或枚举类型能够显式转换到指针。指针转换到足够大小的整型并且再转换会相同的指针类型,它将会具有源指针值。
这意味着该转换不具有未定义行为(指针与整型之间映射在其他方面是实现定义的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用c++编译器编译
int main()
{
uint64_t ival=123;
uint64_t *pval=&ival;
printf("ival address is %x.\n",&ival);
printf("pval contain address is %x.\n",pval);
uint64_t ui64val=(uint64_t)pval;
printf("cast uint64_t* to uint64_t resault is %x.\n",ui64val);
uint64_t *origVal=(uint64_t*)ui64val;
printf("cast uint64_t to uint64_t* resault is %x.\n",origVal);
return 0;
}
// output
ival address is 61fe38.
pval contain address is 61fe38.
cast uint64_t* to uint64_t resault is 61fe38.
cast uint64_t to uint64_t* resault is 61fe38.

[ISO/IEC 14882:2014 §5.2.10.5]A value of integral type or enumeration type can be explicitly converted to a pointer. A pointer converted to an integer of sufficient size (if any such exists on the implementation) and back to the same pointer type will have its original value; mappings between pointers and integers are otherwise implementation-defined. [ Note: Except as described in 3.7.4.3, the result of such a conversion will not be a safely-derived pointer value. — end note ]

函数指针的转换

函数指针能够显示转换到不同类型的函数指针,调用转换后的函数类型的效果与函数定义中的函数不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
void func(int ival)
{
printf("call func,the ival param is %d\n",ival);
}
int main()
{
void(*func_i)(int)=func;
void(*func_i_d)(int,double)=reinterpret_cast<void(*)(int,double)>(func_i);
func_i_d(123,45.6);
return 0;
}

除非转换类型pointer to T1pointer to T2(T1和T2是函数类型),并且转换回它的源类型产生源指针值,这样的指针转换的结果是未指定的(unspecified).

[ISO/IEC 14882:2014 §5.2.10.6]A function pointer can be explicitly converted to a function pointer of a different type. The effect of calling a function through a pointer to a function type (8.3.5) that is not the same as the type used in the definition of the function is undefined. Except that converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [ Note: see also 4.10 for more details of pointer conversions. — end note ]

修改Android的DNS

编辑/system/build.prop加入以下几行:

1
2
3
4
net.rmnet0.dns1=1.1.1.1
net.rmnet0.dns2=8.8.8.8
net.dns1=1.1.1.1
net.dns2=8.8.8.8

保存退出即可(最好重启或者开关一下飞行模式。

tracert命令

路由跟踪,查看数据包访问目标所选择的路径。
Tracert 命令使用用 IP 生存时间 (TTL) 字段和 ICMP 错误消息来确定从一个主机到网络上其他主机的路由.

offsetof不能用在非POD类型(Standard Layout)

offsetof是定义在stddef.h/cstddef中的一个宏,其作用是获取结构成员在结构上的偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A{
public:
char pad7[7];
int ival;
double dval;
};
int main()
{
printf("A::ival offset is %d\n",offsetof(A, ival));
printf("A::dval offset is %d\n",offsetof(A, dval));
return 0;
}
// output
A::ival offset is 8
A::dval offset is 16

但是它不能够用在非Standard Layout Class的类型上,否则是undefine behavior的:

[ISO/IEC 14882:2014 §18.2.4]:The macro offsetof(type, member-designator) accepts a restricted set of type arguments in this International Standard. If type is not a standard-layout class (Clause 9), the results are undefined.The expression offsetof(type, member-designator) is never type-dependent (14.6.2.2) and it is value-dependent (14.6.2.3) if and only if type is dependent. The result of applying the offsetof macro to a field that is a static data member or a function member is undefined. No operation invoked by the offsetof macro shall throw an exception and noexcept(offsetof(type, member-designator)) shall be true.
Note that offsetof is required to work as specified even if unary operator& is overloaded for any of the types involved.

顺便再来复习一下什么叫Standard Layout types

[ISO/IEC 14882:2014 §3.9.9]:Scalar types, standard-layout class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called standard-layout types.

Standard Layout Class则又是:
A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and
  • has no base classes of the same type as the first non-static data member.

即,在类中有这些的都是非标准布局类,offsetof不能用在他们上面。

C++中的Standard Layout types

顺便再来复习一下什么叫Standard Layout types

[ISO/IEC 14882:2014 §3.9.9]:Scalar types, standard-layout class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called standard-layout types.

Standard Layout Class则又是:
A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and
  • has no base classes of the same type as the first non-static data member.

禁用MIUI的更新提示

ROOT手机,安装RE文件管理器。
打开RE,找到路径/system下打开build.prop编辑:

1
ro.build.version.incremental=V9.5.8.0.OCAMIFA

修改为一个大的版本号:

1
ro.build.version.incremental=V99.5.8.0.OCAMIFA

保存后重启即可。

在基类子对象内对this指针做放置new操作

这么做是UB的,直接看标准里的代码吧([ISO/IEC 14882:2014 §3.8.5]):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <cstdlib>
struct B {
virtual void f();
void mutate();
virtual ~B();
};
struct D1 : B { void f(); };
struct D2 : B { void f(); };
void B::mutate() {
new (this) D2; // reuses storage — ends the lifetime of *this
f(); // undefined behavior
... = this; // OK, this points to valid memory
}
void g() {
void* p = std::malloc(sizeof(D1) + sizeof(D2));
B* pb = new (p) D1;
pb->mutate();
&pb; // OK: pb points to valid memory
void* q = pb; // OK: pb points to valid memory
pb->f(); // undefined behavior, lifetime of *pb has ended
}

this的生命周期的终结时机为调用析构函数之后,调用析构函数则意味着该类内的所有数据均变成了无意义的存在,对无意义的东西做操作是UB行为。

MinGW-W64编译32位程序

GCC支持-m32参数来将代码编译到32位程序。但是如果你的MinGW是SEH或者DWARF的异常模型,则他们是单一平台的,不支持编译到32位程序。
stackoverflow上有个回答:How do I compile and link a 32-bit Windows executable using mingw-w64
还有CSDN上的一个问题:MinGW-w64如何编译32位程序
解决办法:可以选择SJLJ的异常模型版本,可以从这里检索下载。也可以使用TDM GCC(目前只更新到了MinGW5.1.0)。

C语言的隐式函数声明

下面的代码:

1
2
3
4
5
6
// hw.c
// 注意没有包含任何头文件
int main(void)
{
printf("HelloWorld!\n");
}

使用gcc编译,是会编译成功并且可以执行的(具有警告):

1
2
3
4
5
6
7
$ gcc hw.c -o hw.exe
test.c: In function 'main':
test.c:2:5: warning: implicit declaration of function 'printf' [-Wimplicit-function-declaration]
printf("HelloWorld!\n");
^~~~~~
test.c:2:5: warning: incompatible implicit declaration of built-in function 'printf'
test.c:2:5: note: include '<stdio.h>' or provide a declaration of 'printf'

那么问题来了,我在当前的编译单元内并没有包含printf的声明,怎么就可以编译通过呢?
因为C语言的历史上支持过implicit function declaration(C89支持函数的隐式声明):

[ISO/IEC 9899:1990 6.3.2.2] If the expression that precedes the parenthesized argument list in a function call consists solely of an identifier. and if no declaration is visible for this identifier, the identifier is implicitly declared exactly as if. in the innermost block containing the function call. the declaration

1
extern int idenfifrer () ;

然而这个特性 C99 就废除掉了:

[ISO/IEC 9899:1999 Foreword] remove implicit function declaration

上面的代码相当于:

  1. 编译器隐式声明了printf函数
  2. 链接器默认链接了stdlib,所以才不会产生符号未定义的链接错误

在gcc编译器如果不想要默认链接可以使用链接器参数(这里只列出其中的三个):

  • -nodefaultlibs: 不使用标准系统库,只有编译参数中指定的库才会传递给链接器
  • -nostdlib: Do not use the standard system startup files or libraries when linking.
  • -nolibc: Do not use the C library or system libraries tightly coupled with it when linking.

更多的gcc链接参数可以看这里:3.14 Options for Linking

虚函数表的存储位置及初始化

注意:C++的多态是实现定义的,所以不同的编译器实现方式或许都不一样,这里只是分析其中的一种(Clang)实现方法,下文中的所有编译命令均使用Clang7.0.0。

C++的多态是通过虚函数表来实现的,但是之前有几个问题不太明白:

  1. 虚表指针是如何初始化的?
  2. 虚函数表是每个实例都有一份吗?
  3. 在基类的构造函数内调用虚函数会有多态行为吗?

带着上面的几个问题,来看下面这个简单的例子:

1
2
3
4
5
6
7
8
// vtable.cpp
class A{
public:
A(){func();}
virtual void func(){printf("A::func\n");}
virtual void func2(){printf("A::func2\n");}
char pad20[20];
};

先来看一下这个类的内存布局(我使用的是Clang(x64)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ clang++ -E vtable.cpp -o vtable_pp.cpp
$ clang++ -cc1 -fdump-record-layouts vtable_pp
# 忽略多余的输出...
*** Dumping AST Record Layout
0 | class A
0 | (A vtable pointer)
8 | char [20] pad20
| [sizeof=32, dsize=28, align=8,
| nvsize=28, nvalign=8]
*** Dumping AST Record Layout
0 | class B
0 | class A (primary base)
0 | (A vtable pointer)
8 | char [20] pad20
| [sizeof=32, dsize=28, align=8,
| nvsize=28, nvalign=8]

可以看到,类A的sizeof为32,因为其尾部内存对齐了4字节。
然后我们来将其编译为汇编代码,我使用的编译器版本是Clang7.0.0:

1
$ clang++ -S vtable.cpp -o vtable.s

找到类A和B的构造函数:

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
# constructor of class B
_ZN1BC2Ev: # @_ZN1BC2Ev
.seh_proc _ZN1BC2Ev
# %bb.0:
subq $56, %rsp
.seh_stackalloc 56
.seh_endprologue
movq %rcx, 48(%rsp)
movq 48(%rsp), %rcx
movq %rcx, %rax
movq %rcx, 40(%rsp) # 8-byte Spill
movq %rax, %rcx
callq _ZN1AC2Ev # 调用基类A子对象的构造函数
leaq _ZTV1B(%rip), %rax # 取出虚函数表的地址
addq $16, %rax
movq 40(%rsp), %rcx # 8-byte Reload
movq %rax, (%rcx)
movq (%rcx), %rax
callq *(%rax)
nop
addq $56, %rsp
retq
# constructor of class A
_ZN1AC2Ev: # @_ZN1AC2Ev
.seh_proc _ZN1AC2Ev
# %bb.0:
subq $40, %rsp
.seh_stackalloc 40
.seh_endprologue
leaq _ZTV1A(%rip), %rax
addq $16, %rax
movq %rcx, 32(%rsp)
movq 32(%rsp), %rcx
movq %rax, (%rcx)
movq (%rcx), %rax
callq *(%rax)
nop
addq $40, %rsp
retq
# 类B的虚函数表
.lcomm _ZStL8__ioinit,1 # @_ZStL8__ioinit
.section .rdata$_ZTV1B,"dr",discard,_ZTV1B
.globl _ZTV1B # @_ZTV1B
.p2align 3
_ZTV1B:
.quad 0
.quad _ZTI1B
.quad _ZN1B4funcEv
.quad _ZN1B5func2Ev
# 类A的虚函数表
.section .rdata$_ZTV1A,"dr",discard,_ZTV1A
.globl _ZTV1A # @_ZTV1A
.p2align 3
_ZTV1A:
.quad 0
.quad _ZTI1A
.quad _ZN1A4funcEv
.quad _ZN1A5func2Ev

可以看到,类A和类B的虚函数表都是存放在_ZTV1B/_ZTV1A,其中存储的为虚函数的函数指针,在.data区的,所以全局只有一份。而且他们的布局为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# LLVM-IR
B:
[4 x i8*]
[
i8* null,
i8* bitcast ({ i8*, i8*, i8* }* @_ZTI1B to i8*),
i8* bitcast (void (%class.B*)* @_ZN1B4funcEv to i8*),
i8* bitcast (void (%class.B*)* @_ZN1B5func2Ev to i8*)
]
A:
[4 x i8*]
[
i8* null,
i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*),
i8* bitcast (void (%class.A*)* @_ZN1A4funcEv to i8*),
i8* bitcast (void (%class.A*)* @_ZN1A5func2Ev to i8*)
]

类的构造函数会从.data区得到虚函数表的地址,并赋值给该实例的vptr,需要注意的是,虚函数表的结构中具有this的偏移(第一个元素)和类的类型信息。
在赋值给实例的vptr时做了偏移:

1
2
leaq _ZTV1A(%rip), %rax
addq $16, %rax

这里是跳过了虚表结构的前两个元素,直接指向了第一个存储虚函数的地址的指针。

而且,更需要说明的是,在B类的构造过程中,会调用A的构造函数,这时B构造函数内的vptr操作还并未执行,所以如果在类A内调用虚函数,则不会有多态行为——因为在此时类实例的vptr指向的是A的虚函数表。

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
class A{
public:
A(){func();}
virtual void func(){printf("A::func\n");}
virtual void func2(){printf("A::func2\n");}
char pad20[20];
};
class B:public A{
public:
B(){func();}
virtual void func(){printf("B::func\n");}
virtual void func2(){printf("B::func2\n");}
};
int main()
{
A* aobj=new B();
delete aobj;
return 0;
}
// ouput
A::func
B::func

Guide to x86-64

Guide to x86-64

增强扫描版pdf

  • 首先下载ImageMagick并安装,注意勾选Install legacy components (convert.exe etc)
  • 然后安装Ghostscript(目的是为了使用convert命令)。
  • 将pdf转为图片

假设原始文件名是origin.pdf, 目标文件名是target.pdf, 下方的convert应替换为实际路径

1
$ convert -density 400 origin.pdf output/%03d.jpg

上方的density参数指像素密度,数字越高图片质量越高体积越大, 如果pdf带文字,就设置300以上吧. 这是影响最终效果的重要参数, 如果过低, 那么下方再神操作效果也不会很好, 过高会导致文件体积过大该指令将整个pdf按页转为多张图片, %03d.jpg表示命名为001.jpg,002.jpg....(超过1000页就应改为%04d.jpg)转换为图片这一步, 是为了获取中间产物进行测试和调整, 通常只需截取一部分页面即可(因为截取一页需要花费数秒时间)

1
2
3
4
#截取第11到第21页
$ convert -density 400 origin.pdf[10-20] output/%04d.jpg
#截取第10页
$ convert -density 400 origin.pdf[9] output/%04d.jpg
  • 测试转换效果
1
$ convert -level 60,97% -quality 50 output/010.jpg preview010.jpg

上方的level参数是指调整图像通道的级别, 60,97%表示灰度低于60%即为黑点, 高于97%为白点. 这里的60,97需要反复调节到自己认为达到最佳效果, 这是整个过程中最重要的参数, 通过该参数实现调整对比度quality指输出jpg文件质量(压缩比), 1-100数字越高质量越高体积越大, 出于减少pdf文件大小的考虑, 应适当调节该参数(举个例子density 400的jpg文件可能有1.4M, 压缩后为700K, 这样最终的pdf文件大小相差一倍, 而肉眼无法察觉页面效果有何区别)

  • 转为pdf

确定了density, level和quality的值之后就可以执行转换了可以从原始pdf转换

1
$ convert -density 400 origin.pdf -level 40,97% -quality 50 target.pdf

也可以从之前生成的jpg转换:

1
$ convert -level 60,97% -quality 50 output/* target.pdf

如果直接从目录转换为pdf打开显示有问题,可以将导出的文件批量处理之后再使用福昕之类的软件合并:

1
find . -name "*.jpg"|xargs -I {} convert -level 60,97% -quality 50 {} {}_.jpg

把当前目录下所有的.jpg增强处理之后另存为*.jpg_.jpg.
然后把它们移动到单独的目录(-I {}的作用是把后续的{}替换为参数):

1
2
$ mkdir finalOutput
$ find . -name "*_.jpg"|xargs -I {} mv {} ./finalOutput

然后使用福昕合并成pdf文件即可(顺便也可以做OCR)。

虚函数表是在何时初始化的?

突然有个疑问,虚函数表是什么时候放进类实例内的?
写了个简单的代码,从LLVM-IR分析一下:

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
class A{
public:
virtual void vfunc_one(int)
{
std::cout<<"A::vfunc_one"<<std::endl;
}
virtual void vfunc_two(int)
{
std::cout<<"A::vfunc_two"<<std::endl;
}
private:
int ival;
};
class B:public A{
public:
virtual void vfunc_one(int)
{
std::cout<<"B::vfunc_one"<<std::endl;
}
virtual void vfunc_two(int)
{
std::cout<<"B::vfunc_two"<<std::endl;
}
char cval;
};
int main(int argc,char* argv[])
{
B bobj;
return 0;
}

再来看一下对象布局和内存对齐:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// class object layout
*** Dumping AST Record Layout
0 | class A
0 | (A vtable pointer)
8 | int ival
| [sizeof=16, dsize=12, align=8,
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class B
0 | class A (primary base)
0 | (A vtable pointer)
8 | int ival
12 | char cval
| [sizeof=16, dsize=13, align=8,
| nvsize=13, nvalign=8]
// class memory align
%class.B = type { %class.A.base, i8, [3 x i8] }
%class.A.base = type <{ i32 (...)**, i32 }>
%class.A = type <{ i32 (...)**, i32, [4 x i8] }>

可以看到,在Clang的实现中,vptr是在对象空间的首部,是一个32位指针大小。

上面的C++代码相关的LLVM-IR代码为:

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
; Function Attrs: noinline norecurse nounwind optnone uwtable
define i32 @main(i32, i8**) #4 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
%6 = alloca %class.B, align 8
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
call void @_ZN1BC2Ev(%class.B* %6) #3
ret i32 0
}
// B::B()
; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr void @_ZN1BC2Ev(%class.B*) unnamed_addr #5 comdat align 2 {
%2 = alloca %class.B*, align 8
store %class.B* %0, %class.B** %2, align 8
%3 = load %class.B*, %class.B** %2, align 8
%4 = bitcast %class.B* %3 to %class.A*
call void @_ZN1AC2Ev(%class.A* %4) #3
%5 = bitcast %class.B* %3 to i32 (...)***
// set vptr
store i32 (...)** bitcast (i8** getelementptr inbounds ({ [4 x i8*] }, { [4 x i8*] }* @_ZTV1B, i32 0, inrange i32 0, i32 2) to i32 (...)**), i32 (...)*** %5, align 8
ret void
}

可以看到,是从构造函数里面对vptr执行初始化的...

Clang查看对象的内存布局

clang可以在编译时使用-cc1 -fdump-record-layouts参数来查看对象的内存布局。
但是使用上面的命令不会从Path路径查找标准头文件,我们需要先对源文件进行预处理:

1
$ clang++ gcc -E main.c -o main_pp.cpp

然后对预处理之后的.cpp文件执行编译时加入-cc1 -fdump-record-layouts参数:

1
$ clang++ -cc1 -fdump-record-layouts main_pp.cpp

Example:

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
// class_layout.cpp
class A{
public:
virtual void func(int ival=0){}
};
class B:public A{
public:
virtual void func(int ival=123){}
};
class C:public B{
public:
virtual void func(int ival=123){}
};
int main()
{
C cobj;
cobj.func();
B &bobj=cobj;
bobj.func();
A &aobj=cobj;
aobj.func();
}

预处理:

1
$ clang++ -E class_layout.cpp -o class_layout_pp.cpp

查看上面三个类的内存布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ clang++ -cc1 -fdump-record-layouts class_layout_pp.cpp
*** Dumping AST Record Layout
0 | class A
0 | (A vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]
*** Dumping AST Record Layout
0 | class B
0 | class A (primary base)
0 | (A vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]
*** Dumping AST Record Layout
0 | class C
0 | class B (primary base)
0 | class A (primary base)
0 | (A vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]

参考文章:Dumping a C++ object's memory layout with Clang

虚函数的默认参数根据调用者(指针或引用)的静态类型决定

之前曾经在C/C++标准的一些摘录#override函数不会覆盖其原有默认参数中曾经写道过这点内容,但是不够详细,这里做一个补充。

考虑以下代码:

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
class A{
public:
virtual void func(int ival=0)
{
std::cout<<"A::func,arg is "<<ival<<endl;
}
};
class B:public A{
public:
virtual void func(int ival=123)
{
std::cout<<"B::func,arg is "<<ival<<endl;
}
};
class C:public B{
public:
virtual void func(int ival=456)
{
std::cout<<"C::func,arg is "<<ival<<endl;
}
};
int main()
{
C cobj;
cobj.func(); // print what?
B &bobj=cobj;
bobj.func(); // print what?
A &aobj=cobj;
aobj.func(); //print what?
}
// output
C::func,arg is 456
C::func,arg is 123
C::func,arg is 0

C++标准中规定了关于虚函数的默认参数使用描述:

[ISO/IEC 14882:2014]A virtual function call (10.3) uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object.

即,虚函数的默认参数的使用是执行该虚函数调用的指针或引用的静态类型的决定的。
从上面的例子来看:

1
2
3
4
5
6
7
8
9
int main()
{
C cobj;
cobj.func(); // 执行虚函数调用的对象的静态类型是C,则本次调用的默认参数是C::func声明的,即456
B &bobj=cobj;
bobj.func(); // 执行虚函数调用的对象的静态类型是B,则本次调用的默认参数是B::func,即123
A &aobj=cobj;
aobj.func(); // 执行虚函数调用的对象的静态类型是A,则本次调用的默认参数是A::func,即0
}

即多态函数的默认参数并不是动态绑定的,并不会在运行时才确定使用继承层次中的哪一个实现的默认参数,而是编译时根据对象的类型就确定了使用哪个默认参数。
可以看一下上面代码的LLVM-IR代码:

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
define i32 @main() #4 {
%1 = alloca %class.C, align 8
%2 = alloca %class.B*, align 8
%3 = alloca %class.A*, align 8
// class C的构造函数
call void @_ZN1CC2Ev(%class.C* %1) #3
// 直接通过C类型的对象cobj调用func
call void @_ZN1C4funcEi(%class.C* %1, i32 456)
// 通过B类型的引用bobj用来调用func
%4 = bitcast %class.C* %1 to %class.B*
store %class.B* %4, %class.B** %2, align 8
%5 = load %class.B*, %class.B** %2, align 8
%6 = bitcast %class.B* %5 to void (%class.B*, i32)***
%7 = load void (%class.B*, i32)**, void (%class.B*, i32)*** %6, align 8
%8 = getelementptr inbounds void (%class.B*, i32)*, void (%class.B*, i32)** %7, i64 0
%9 = load void (%class.B*, i32)*, void (%class.B*, i32)** %8, align 8
// 该调用的默认参数在编译时已经确定为B::func的版本,不会推迟到动态绑定时
call void %9(%class.B* %5, i32 123)
// 通过A类型的引用aobj来调用func
%10 = bitcast %class.C* %1 to %class.A*
store %class.A* %10, %class.A** %3, align 8
%11 = load %class.A*, %class.A** %3, align 8
%12 = bitcast %class.A* %11 to void (%class.A*, i32)***
%13 = load void (%class.A*, i32)**, void (%class.A*, i32)*** %12, align 8
%14 = getelementptr inbounds void (%class.A*, i32)*, void (%class.A*, i32)** %13, i64 0
%15 = load void (%class.A*, i32)*, void (%class.A*, i32)** %14, align 8
// 该调用的默认参数在编译时已经确定为A::func的版本,不会推迟到动态绑定时
call void %15(%class.A* %11, i32 0)
// ...
}

Windwos上使用MinGW编译LLVM

首先,下载LLVMClang源码,并解压。
将Clang的源码(cfe-6.0.1.src)目录改名clang后放到LLVM源码的tools目录下($LLVM_SRC_ROOT/tools/clang)。
在LLVM源码目录右键打开Git Bash终端,执行下列命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ mkdir build
$ cd build
# cmake参数
# G为要生成的格式,用MinGW,这里大小写必须写对,写错的话会有提示。
# CMAKE_BUILD_TYPE为构建类型,可以写Debug,Release,MinSizeRel。
# CMAKE_INSTALL_PREFIX为install路径,一般这里路径最好保守一些,尽量不要用中文或空格。
# CMAKE_MAKE_PROGRAM这里写不写都可以,如果用Ninja编译的话,这里就必须写了。
$ cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=D:\LLVM -DCMAKE_MAKE_PROGRAM=mingw32-make.exe ..
# 执行编译
$ mingw32-make
# 编译完毕后执行安装
$ mingw32-make install

安装完之后就可以在CMAKE_INSTALL_PREFIX指定的目录看到编译完的LLVM和Clang了,将其添加到系统Path路径即可。
我编译好的版本(目前最新LLVM 6.0.1),编译环境为MinGW64-x86_64-6.2.0-posix-seh-rt_v5-rev1,可以在这里下载。
注:其他的细节可以看Clang源码目录下的INSTALL.TXT文件。

  • 2018.09.27 这几天LLVM出了7.0.0版本,果然是版本帝。我又编译了一个7.0.0版本的,编译环境与上文相同:点此下载

解决cmder中文乱码


打开cmder设置Startup-Environment,添加 set LANG=zh_CN.UTF-8即可。

UE4接入SteamSDK

编辑Config\DefaultEngine.ini,加入以下内容:

1
2
3
4
5
6
7
8
9
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver")
[OnlineSubsystem]
DefaultPlatformService=Steam
PollingIntervalInMs=20
[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480

注意修改SteamDevAppId项为你自己的Steam内容的AppID.

然后使用Shipping模式打包,打包完成后在打包输出的$ProjectName\Binaries\Win64下新建文件steam_appid.txt,将你的AppID填入其中即可(如480(Steam的测试AppID))。

apk的反编译与签名

  1. 安装java环境,jdk下载
  2. 下载最新版本的apktool.jar,并且重命名为apktool.jar
  3. 保存下列脚本为apktool.bat
1
2
3
4
5
@echo off
if "%PATH_BASE%" == "" set PATH_BASE=%PATH%
set PATH=%CD%;%PATH_BASE%;
chcp 65001 2>nul >nul
java -jar -Duser.language=en -Dfile.encoding=UTF8 "%~dp0\apktool.jar" %*

然后将apktool.bat和apktool.jar放在同一个目录下。或者直接下载我打包的apktool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 反编译apk
$ apktool.bat d -o <output_dir> <input.apk>
# 修改完成后重新编译apk
$ apktool.bat b -o <output.apk> <input_dir>
# 生成keystore文件
# -genkey 产生证书文件
# -alias 产生别名
# -keystore 指定密钥库的.keystore文件中
# -keyalg 指定密钥的算法,这里指定为RSA(非对称密钥算法)
# -validity 为证书有效天数,这里我们写的是40000天
$ keytool -genkey -alias demo.keystore -keyalg RSA -validity 40000 -keystore demo.keystore
# 签名到appk,记得替换<input.apk>为需要签名的apk文件
$ jarsigner -verbose -keystore demo.keystore <input.apk> demo.keystore

C++ Lambda的捕获

在之前的一篇文章中(lambda在编译器中实现的方式)写道过编辑器中Lambda的结果实际上就是一个重载了()的类。
但是关于捕获的地方有一点需要补充:如何确定捕获的个数?如果使用值捕获[=]或者引用捕获[&]会把之前的所有数据都作为该Lambda的数据成员吗?

带着这个问题,来探究一下。
首先,先说结论:在使用默认捕获[&]/[=]时,并不会把上文中所有的对象都捕获进来,而是在Lambda表达式内部用到了哪几个才会捕获。

看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public:
int a=456;class A{
A(){}
A(const A& aobj):a{aobj.a}{}
};
int main()
{
int a=123;
A aobj;
auto lambdaObj=[=](){cout<<a<<'\t'<<aobj.a<<endl;};
cout<<sizeof(lambdaObj)<<endl;
lambdaObj();
}
// output
8
123 456

先来看一下这个闭包对象的成员(LLVM-IR):

1
2
3
4
5
// class A
%class.A = type { i32 }
// lambdaObj class type
%class.anon = type { i32, %class.A }

因为是值捕获,所以该闭包对象捕获的成员均是一份拷贝。

本来我以为,生成出来的闭包对象应该有一个构造函数,捕获的参数作为构造的参数传进去,不知道是LLVM做了优化还是怎样,没看到有生成出来构造函数,其捕获的初始化部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
define i32 @main() #4 {
%1 = alloca i32, align 4
%2 = alloca %class.A, align 4
store i32 123, i32* %1, align 4
call void @_ZN1AC2Ev(%class.A* %2)
// 生成出来的闭包对象
%3 = alloca %class.anon, align 4
// 将捕获的参数赋值为类的参数
%4 = getelementptr inbounds %class.anon, %class.anon* %3, i32 0, i32 0
%5 = load i32, i32* %1, align 4
store i32 %5, i32* %4, align 4
%6 = getelementptr inbounds %class.anon, %class.anon* %3, i32 0, i32 1
// 调用闭包对象内类成员A的拷贝构造A::A(const A&)
call void @_ZN1AC2ERKS_(%class.A* %6, %class.A* dereferenceable(4) %2)
// ...
}

函数类型的typedef不能用在定义

考虑如下情况:

1
2
3
typedef void Func(int);
Func TestFunc; // OK
Func TestFunc2{} // ill-formed

[ISO/IEC 14882:2014 §8.3.5.10]A typedef of function type may be used to declare a function but shall not be used to define a function (8.4).

树莓派命令行配置连接Wifi

在树莓派上的Wifi连接信息是保存在/etc/wpa_supplicant/wpa_supplicant.conf文件中的:

1
2
3
4
5
6
7
8
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
ssid="APName"
psk="password"
key_mgmt=WPA-PSK
}

照着network中的配置信息把ssid与psk填上就可以了。
注意:优先连接的顺序是配置的顺序,如果你希望优先连接某一个AP,那么将它的配置挪到最前面。

关闭Windows休眠来释放磁盘空间

Windows下的休眠文件hiberfil.sys一般很大,都有十几个G,而我一般也很少用到休眠功能,可以使用下面的命令关闭。
以管理员权限运行cmd:

1
powercfg -h off

重启系统即可,hiberfil.sys所占的空间就会被释放了。

远程控制下切换窗口到另一个屏幕

有些VNC工具对多屏幕支持不友好,只能显示一个屏幕上的窗口内容,如果所有的打开窗口默认都在另一个屏幕,则软件都操作不了了。
Windows移动窗口的快捷键是:
Alt+Space出现菜单,然后按M,就是移动功能。
可以使用鼠标或者上下左右键来移动窗口到远程可以看到的屏幕。

Windows窗口快捷键

最大化:在窗口下按:Alt+Space+X
最小化:在窗口下按:Alt+Space+N

Wget下载网站

1
$ wget -c -r -np -k -L -p api.unrealengine.com/INT/API/

参数如下:

-c 断点续传
-r 递归下载,下载指定网页某一目录下(包括子目录)的所有文件
-nd 递归下载时不创建一层一层的目录,把所有的文件下载到当前目录
-np 递归下载时不搜索上层目录,如wget -c -r www.xxx.org/pub/path/
没有加参数-np,就会同时下载path的上一级目录pub下的其它文件
-k 将绝对链接转为相对链接,下载整个站点后脱机浏览网页,最好加上这个参数
-L 递归时不进入其它主机,如wget -c -r www.xxx.org/
如果网站内有一个这样的链接:
www.yyy.org,不加参数-L,就会像大火烧山一样,会递归下载www.yyy.org网站
-p 下载网页所需的所有文件,如图片等
-A 指定要下载的文件样式列表,多个样式用逗号分隔
-i 后面跟一个文件,文件内指明要下载的URL

Linux查看nohup.out

1
$ tail -fn 50 nohup.out

Lambda-Expressions Syntax

首先先来看一下C++标准中对于Lambda-Expressions的Syntax描述(注意也是递归描述的):
index
下面这个代码表达了什么意思?

1
2
3
4
5
6
int main()
{
[](){};
[]{}();
{}[]{};
}

先看第一行:

1
[](){};

这一行是使用lambda-expression声明了一个Unamed的闭包对象(closure object),不捕获、不传参也不做任何实现。

第二行:

1
[]{}();

这个就有点意思了,根据上面的Lambda-Expression Syntax图里标注的那样:${lambda\textrm{-}declarator}_{opt}$是Opt的,表示可以省略。
而${lambda\textrm{-}declarator}$又包括${(parameter\textrm{-}declaration\textrm{-}clause) mutable}_{opt}$等,所以表示lambda表达式在声明时参数列表可以省略。
也就是说:

1
2
3
auto lambdaObj=[](){};
// 可以写为
audo lambdaObj=[]{};

这样我们可以理解第二行的前半部分为声明一个闭包对象(closure object),而最后的那个();则是调用该闭包对象。

第三行:

1
{}[]{};

其实这一行也可以这么写:

1
2
3
4
{
}
[]{};

一个block然后使用lambda-expression创建一个Unamed的闭包对象(closure object)。

WSL换源

WSL的Ubuntu使用的是Ubuntu 16.04版本,可以通过lsb_release -a查看:

1
2
3
4
5
6
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.3 LTS
Release: 16.04
Codename: xenial

备份/etc/apt/sources.list,然后替换其中的内容为国内阿里源:

1
2
3
$ sudo cd /etc/apt
$ sudo mv sources.list sources.list.backup
$ sudo nano sources.list

把下面的内容填入其中,保存退出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
deb-src http://archive.ubuntu.com/ubuntu xenial main restricted #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb http://mirrors.aliyun.com/ubuntu/ xenial multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse #Added by software-properties
deb http://archive.canonical.com/ubuntu xenial partner
deb-src http://archive.canonical.com/ubuntu xenial partner
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse

然后更新即可:

1
$ sudo apt-get update

清理Mstsc的历史记录

清理mstsc的历史记录只需要把下面这个注册表项里的记录删除即可:

1
[HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default]

修改Win10远程连接的默认端口

这几天使用frp做内网穿透,在公司也可以通过mstsc来访问内网的电脑,但是使用默认的端口(3389)感觉又不安全(总有无良开扫描器的),研究了一下怎么修改win10默认的远程连接端口,具体操作如下。

打开下面两个注册表项,依次执行下列操作:

1
2
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Terminal Server/Wds/rdpwd/Tds/tcp
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Terminal Server/WinStations/RDP-Tcp

在这两个注册表项的右侧找到PortNamber记录,可以看见其默认值是3389,修改成所希望的端口(<65535)即可(如11111),注意使用十进制。

然后还需要修改防火墙的放行端口,因为Windows的远程连接默认端口是3389,所以3389防火墙是默认放行的,但是上面修改之后的端口是不行的,所以要修改一下。
依次打开这两个注册表项,执行下面的操作:

1
2
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/SharedAccess/Defaults/FirewallPolicy/FirewallRules
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/SharedAccess/Parameters/FirewallPolicy/FirewallRules

在这两个注册表项的右侧找到这两个记录:RemoteDesktop-UserMode-In-TCPRemoteDesktop-UserMode-In-UDP:
他们的值默认情况下类似下面这样:

1
v2.28|Action=Allow|Active=FALSE|Dir=In|Protocol=6|LPort=3389|App=%SystemRoot%\system32\svchost.exe|Svc=termservice|Name=@FirewallAPI.dll,-28775|Desc=@FirewallAPI.dll,-28756|EmbedCtxt=@FirewallAPI.dll,-28752|

我们只需要将这两个记录里面的LPort改为我们上面修改的端口(11111)即可。

然后就可以在控制面板/系统和安全/防火墙/高级设置/入站规则里点刷新之后看到我们修改之后的端口了(我的端口已打码..):

查看Linux系统上所有的服务

1
$ service --status-all

注:在我的系统版本(ubuntu 16.04.3 LTS)上iptables的服务是ufw

Linux防火墙放行端口

如果是使用ufw服务的话可以使用这种方法:

1
2
$ ufw allow 6060/udp
$ ufw allow 6060/tcp

允许特定端口范围通过防火墙
使用 UFW 指定端口范围时,必须指定规则适用的协议( tcp 或 udp ),以下为示例:

1
2
$ ufw allow 10000:10100/tcp #允许 10000~10100 端口的 tcp 通过防火墙
$ ufw allow 10000:10100/udp #允许 10000~10100 端口的 udp 通过防火墙

如果是使用iptable服务的话可以使用下面的方法:

1
2
3
4
# 接受从端口6060的入站的UDP连接
$ iptables -A INPUT -p udp --destination-port 6060 -j ACCEPT
# 丢弃从6060入站的UDP数据包
$ iptables -A INPUT -p udp --destination-port 6060 -j DROP

更多ufw的用法:Ubuntu 18.04 使用 UFW 设置防火墙

iptables的参数:

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
iptables v1.4.21
Usage: iptables -[ACD] chain rule-specification [options]
iptables -I chain [rulenum] rule-specification [options]
iptables -R chain rulenum rule-specification [options]
iptables -D chain rulenum [options]
iptables -[LS] [chain [rulenum]] [options]
iptables -[FZ] [chain] [options]
iptables -[NX] chain
iptables -E old-chain-name new-chain-name
iptables -P chain target [options]
iptables -h (print this help information)
Commands:
Either long or short options are allowed.
--append -A chain Append to chain
--check -C chain Check for the existence of a rule
--delete -D chain Delete matching rule from chain
--delete -D chain rulenum
Delete rule rulenum (1 = first) from chain
--insert -I chain [rulenum]
Insert in chain as rulenum (default 1=first)
--replace -R chain rulenum
Replace rule rulenum (1 = first) in chain
--list -L [chain [rulenum]]
List the rules in a chain or all chains
--list-rules -S [chain [rulenum]]
Print the rules in a chain or all chains
--flush -F [chain] Delete all rules in chain or all chains
--zero -Z [chain [rulenum]]
Zero counters in chain or all chains
--new -N chain Create a new user-defined chain
--delete-chain
-X [chain] Delete a user-defined chain
--policy -P chain target
Change policy on chain to target
--rename-chain
-E old-chain new-chain
Change chain name, (moving any references)
Options:
--ipv4 -4 Nothing (line is ignored by ip6tables-restore)
--ipv6 -6 Error (line is ignored by iptables-restore)
[!] --protocol -p proto protocol: by number or name, eg. `tcp'
[!] --source -s address[/mask][...]
source specification
[!] --destination -d address[/mask][...]
destination specification
[!] --in-interface -i input name[+]
network interface name ([+] for wildcard)
--jump -j target
target for rule (may load target extension)
--goto -g chain
jump to chain with no return
--match -m match
extended match (may load extension)
--numeric -n numeric output of addresses and ports
[!] --out-interface -o output name[+]
network interface name ([+] for wildcard)
--table -t table table to manipulate (default: `filter')
--verbose -v verbose mode
--wait -w wait for the xtables lock
--line-numbers print line numbers when listing
--exact -x expand numbers (display exact values)
[!] --fragment -f match second or further fragments only
--modprobe=<command> try to insert modules using this command
--set-counters PKTS BYTES set the counter during insert/append
[!] --version -V print package version.

一些参数缩写:

  • --sport is short for --source-port
  • --dport is short for --destination-port

Linux下递归删除特定文件

如果我们期望递归删除当前目录下的所有Binaries文件夹,可以使用以下命令:

1
$ find . -name "Binaries" | xargs rm -rf

就是先通过find找到当前目录下所有的Binaries文件夹,然后通过xargs这个构造参数列表并运行rm -rf命令。

C++声明语义的一个坑点

以下代码有什么问题?会输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class T
{
public:
void print()
{
std::string(hw);
std::cout<<hw<<std::endl;
}
private:
std::string hw{"helloworld"};
};
int main(int argc,char* argv[])
{
T Obj;
Obj.print();
return 0;
}

答案是什么都不会输出!
真正执行的语义并不是我们期望的那样:创造一个std::string的临时变量,然后执行临时变量的销毁语义。
因为C++的声明语义规则如下;

注意上面图里是递归描述的。

1
2
3
4
5
std::string(hw);
// 等价于
std::string hw;
// 当然这么也是等价的
std::string ((((((hw))))));

所以在print函数里,是创造了一个std::string的局部变量(local-scope)hw,把类范围(class-scope)的hw给隐藏了。
解决办法:使用{}代替(),上面的例子里使用initializer-list会调用copy-constructor,然后立刻销毁该临时对象。

在当前的这个例子里,还没什么危害,如果在多线程编程里对锁进行这样的操作那就十分令人窒息了。

在WSL中跑SS和KCP

最近公司把shadowsocks-windows的进程给禁用了,ss-qt的版本有很多问题十分难用。想着继续用其他SS的win客户端依然治标不治本,我把shadowsocks-python跑在了Win10的WSL里,感觉用起来比win客户端稳定很多...
注:kcp的客户端也可以跑在WSL里,直接从kcptun/release下载linux-amd64版本的即可,具体用法见./client_linux_amd64 -h的参数。

1
2
3
4
5
# start kcp
$ nohup /etc/client_linux_amd64 --localaddr ${KCP_LOCAL_ADDR} --remoteaddr ${KCP_SERVER_ADDR:KCP_SERVER_PORT} --key ${KCP_PASSWORD} --crypt none --mode fast3 --mtu 1350 --sndwnd 512 --rcvwnd 512 --datashard 10 --parityshard 3 --dscp 0 --quiet false >/dev/null 2>&1 &
# start ss(using kcp)
$ python /usr/local/lib/python2.7/dist-packages/shadowsocks/local.py -s 127.0.0.1 -p ${KCP_LOCAL_ADDR} -k ${SS-SERVER-PASSWORD} -b 0.0.0.0 -l ${SS-LOCAL-PORT} -m aes-256-cfb -t 600

注:其中shadowsocks的-b参数为0.0.0.0,表示允许来自局域网的连接(注意关闭防火墙)。

这样就不需要读配置文件了,但注意各项参数都改为你自己的配置值。

VC越界输出烫烫烫的原因

在当年用VC学习C语言时,在遇到内存访问越界的时候会看到输出烫烫烫等"类似乱码"的东西。
例如下面的代码(使用vs2017-debug-x86编译):

1
2
3
4
5
int main()
{
char NoInitCharArray[10];
std::cout << NoInitCharArray << std::endl;
}

x32dbg调试:

通过IDA调试:

运行结果:

其实时因为,VC在debug模式下,会将未初始化的内存设置为0xCC,而中文的GBK编码下0xCC恰好就是

IDA的插件和配置

注:版本为IDA 7.0 Pro
我的IDA配色插件zyantific/IDASkins和主题:ida_skin,效果如下:

还有我编译的支持IDA7.0的REhints/HexRaysCodeXplorer:ida7_HexRaysCodeXplorer.

IDA安装findcrypt

1
2
3
4
5
# 可能需要先更新pip
$ python -m pip install --upgrade pip
# 之后安装 yara-python
$ pip install yara-python

然后将findcrypt-yara仓库里的findcrypt2.pyfindctypr3.rules这两个文件放在进入Ida/plugins目录下,打开IDA即可在插件目录看到findcrypt了。

Abd刷入Recovery

下载Adb,然后根据具体情况使用下列命令(如果当前已经在bootloader就不需要执行第一条了)。

1
2
3
4
5
6
adb reboot bootloader
# 写入img到设备
fastboot flash recovery recovery.img
fastboot flash boot boot.img
# 引导img
fastboot boot recovery.img

删除包含某些字符的行

在Linux下可以使用sed命令:

1
2
3
4
5
6
7
8
# 将abc.txt中所有含有"ABC"这个连续字符的行删除并写入到log.txt中
sed -e '/ABC/d/' abc.txt > log.txt
# 将自身所有含有"ABC"这个连续字符的行删除并保存
sed -i -e '/ABC/d' abc.txt
# 要删除的字符参数可以指定多个,只需要指定多个-e
# 将含有ABC和DEF的行都删除
sed -i -e '/ABC/d' -e '/DEF/d' abc.txt

其中-e的参数ABC也可以使用正则表达式来代替。

启用Chrome自动禁用的插件

通过运行->gepdit进入组策略。
计算机配置->管理模板右键选择添加/删除模板(A),导入Chrome.adm
然后在管理模板下就会有经典管理模板(ADM),依次展开Google->GoogleChrome->扩展程序;双击右侧配置扩展程序安装白名单,启用它并把我们想要启用的扩展程序的ID(扩展程序ID可以从Chrome->扩展程序中查看)填入其中保存即可,重新打开Chrome,那个插件就可以启用了。
Enable-Chrome-Local-Plugin

注:将Cortana的搜索默认使用Chrome以及Google.
使Cortana默认使用Google作为搜索引擎的插件CortanaUseGoogle.crx(需要使用上面的方式将该插件加入到扩展程序安装白名单中).同时组合EdgeDeflector可以将Cortana使用的默认浏览器由Edge改为Chrome.

VS附加到进程的错误

今天使用VS的Debug->Attach to process产生了一个错误:

MS2017-AttachToProcessField

1
Unable to attach to the process.The Visual Studio 2017 remote Debugger(MSVCMON.EXE) does not appear to be running on the remote computer.This may be because a firewall is preventing communication to the remote computer.Please see Help for assistance on configuring remote debugging.

本来以为是VS的配置问题,后来才发现这是因为我在Proxifier中把电脑上的所有流量转发到了SS,没想到VS的本地调试也是走网络的,流量通过代理之后一直连接不到本地计算机就会出现这个问题,全局代理需谨慎。

Separate memory location

[ISO/IEC 14882:2014 §1.7.5] A structure declared as:

1
2
3
4
5
6
7
8
struct {
char a;
int b:5,
c:11,
:0,
d:8;
struct {int ee:8;} e;
}

contains four separate memory locations: The field a and bit-fields d and e.ee are each separate memory locations, and can be modified concurrently without interfering with each other. The bit-fields b and c together constitute the fourth memory location. The bit-fields b and c cannot be concurrently modified, but b and a, for example, can be.

Clang的一个Bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A{
public:
void test(int){
printf("aaa\n");
}
};
extern "C" void _ZN1A4testEi(A*,int){
printf("bbb\n");
}
int main()
{
A a;
a.test(123);
return 0;
}

上面的代码通过Clang编译运行会输出bbb(使用最新的Clang5.0也是如此),而GCC则产生一个重定义错误。
先挖坑了,有时间再分析一下这个问题。

ISO C与POSIX对Byte定义的区别

ISO C

byte
addressable unit of data storage large enough to hold any member of the basic character set of the execution environment
NOTE 1 It is possible to express the address of each individual byte of an object uniquely.
NOTE 2 A byte is composed of a contiguous sequence of bits, the number of which is implementation-defined. The least significant bit is called the low-order bit; the most significant bit is called the high-order bit.

POSIX

Byte
An individually addressable unit of data storage that is exactly an octet, used to store a character or a portion of a character; see also Section 3.87 (on page 47). A byte is composed of a contiguous sequence of 8 bits. The least significant bit is called the ‘‘low-order’’ bit; the most significant is called the ‘‘high-order’’ bit.
Note: The definition of byte from the ISO C standard is broader than the above and might accommodate hardware architectures with different sized addressable units than octets.

LaTex插入符号

空格

含义指令效果解释
两个quad空格a \qquad b$a \qquad b$两个m的宽度
quad空格a \quad b$a \quad b$一个m的宽度
大空格a\ b$a\ b$1/3m宽度
中等空格a\;b$a\;b$2/7m宽度
小空格a\,b$a\,b$1/6m宽度
没有空格ab$ab$none
紧贴a!b$a!b$缩进1/6m宽度

\quad1ememm代表当前字体下接近字符‘M’的宽度(approximately the width of an "M" in the current font).

分隔符

使用\textrm{-}插入分隔符-不会使其被识别为减号。

1
declaration\textrm{-}specifiers_{opt}

效果如下:

$$declaration\textrm{-}specifiers_{opt}$$

sizeof size of reference member of class

1
2
3
4
5
6
7
struct ATest{
ATest(int &x):y(a){}
int &y;
}
// The size?
sizeof(ATest);

上面类ATest在LLVM/Clang下编译的内存布局为:

1
%struct.ATest = type { i32* }

至于引用为什么是指针,具体请看引用的实现
所以sizeof(ATest)在这个实现下的结果是8。

What is Translation Unit in C/C++?

[ISO/IEC 14882:2014]A source file together with all the headers (17.6.1.2) and source files included (16.2) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (16.1) preprocessing directives, is called a translation unit. [ Note: A C ++ program need not all be translated at the same time. — end note ]

[ISO/IEC 9899:1999]
A source file together with all the headers and source files included via the preprocessing directive #include is known as a preprocessing translation unit. After preprocessing, a preprocessing translation unit is called a translation unit.

Linux增加SWAP大小

有时候给虚拟机能够实际分配的内存大小不能满足于虚拟机的需求,可以扩大SWAP来临时解决,方法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建1G大小的文件(/swap_file为指定存放该文件路径及名字)
$ sudo dd if=/dev/zero of=/swap_file bs=1M count=1024
# 改变创建的swap_file文件权限:
$ sudo chmod 600 /swap_file
# 使用mkswap设置上面创建的文件为SWAP文件
$ sudo mkswap /swap_file
Setting up swapspace version 1, size = 1048572 KiB
no label, UUID=f6286522-5b12-46d9-a4a3-ef151547841c
# 启用SWAP文件
$ sudo swapon /swap_file
# 在fstab文件中加入一条记录,开机时挂载该SWAP文件
$ sudo vim /etc/fstab
# 在/etc/fstab中加入下面一行内容
/swap_file swap swap defaults 0 0

Linux静态IP设置

下面需要修改的文件路径为/etc/network/interface.

1
2
3
4
5
6
7
8
9
10
11
# The loopback network interface
auto lo
iface lo inet loopback
# The primry network interface
auto eth0
# iface eth0 inet dhcp 为自动获取分配IP
iface eth0 inet static
address 192.168.2.111
netmask 255.255.255.0
geteway 192.168.2.1

一些cmd命令技巧

cmd命令里是让他们同时执行几个命令:

1
2
start cmd.exe /c ""
start cmd.exe /c ""

在上面双引号内输入要执行的命令,保存为.bat;运行时会同时打开两个命令行窗口。
cmd/c的意思为执行完命令后关闭命令窗口;如果想要执行完不关闭可以使用/k

cmd中让命令在后台执行不会阻塞后续命令的方式(类似于linux命令后加&):

1
2
start /b cmd.exe /c ""
start /b cmd.exe /c ""

start加入/b,这两条命令就不会阻塞(不会等到第一条命令执行完毕之后再执行第二条)。

.bat后台执行,有些.bat我们希望开机启动,这时候开机弹出黑框框就不太好,可以使用下面的.vbs脚本来执行.bat

1
2
3
DIM objShell
set objShell=wscript.createObject("wscript.shell")
iReturn=objShell.Run("cmd.exe /C hideRuningBat.bat", 0, TRUE)

C语言跨scope的变量访问

如果具有下面的C语言代码,并且使用C编译器编译。

1
2
3
4
5
6
7
int i=123;
void foo(void){
int i =456;
// 如何访问外部的i?
printf("%d",i);
}

因为在C语言中因为没有namespace的概念,所以不能用C++中的::来限定操作。
但是可以使用下面的骚操作来实现:

1
2
3
4
5
6
7
8
int i=123;
void foo(){
int i=456;
{
extern int i;
printf("%d\n",i);
}
}

Google C++ Style Guile

简易图片版:

中文PDF版:

C++17特性速查表

值类别速查表

浮点的零值

浮点的零值应该用0.0f,因为浮点值并不是精确存储的,有浮点舍入,所以不能用位全为0的0.0比较。

sendfile的优势

通常,我们从一个文件描述符fd中读取数据,然后再写入另一个fd:

1
2
while ((n = read(diskfilefd, buf, BUZ_SIZE)) > 0)
write(sockfd, buf, n);

但是频繁传输使用这样的方式很不高效,为了传输文件必须使用两个系统调用:一个用来将文件内容从内核缓冲区cache拷贝到用户空间,另一个来将用户空间缓冲区拷贝至内核空间,以此进行传输。
但是如果我们只是传输而不对传输的数据进行任何处理这种两步式的操作就是一种浪费。
系统调用sendfile就是被设计为来抵消这种低效性。
Unix system call interface:sendfile

1
2
3
#include <sys/sendfile.h>
// retuens number of bytes transferres,or -1 on error
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

其作用为:

transfer data between file descriptors

比如,当我们通过socket描述符来传输数据时,通过sendfile会直接将数据传送到套接字上,而不会经过用户空间,这种技术叫做零拷贝传输(zero-copy transfer)

系统调用sendfile在代表输入文件的描述符in_fd和代表输出的描述符out_fd之间传送文件内容(字节)。描述符out_fd必须指向一个套接字。参数in_fd指向的文件必须是可以进行mmap()操作的。在实践中,这通常表示一个普通文件,这些局限多少限制了sendfile的使用。我们可以使用sendfile将数据从文件传递到套接字上,但反过来就不行。另外,我们也不能通过sendfile在两个套接字之间直接传送数据。

在Linux2.4以及早期版本中,out_fd是可以指向一个普通文件的。

C++11的新特性与兼容性

new、构造函数和异常

new操作实际上是由两个部分组成的:

  1. 首先调用operator new分配内存
  2. 调用对象的构造函数

Evaluation of a new expression invokes one or more allocation and constructor functions; see 5.3.4

这很重要,虽然一般情况下没有什么问题,但是考虑到异常安全这就是很重要的:如果new一个对象时抛出异常如何判断是operator new抛出了异常还是类的构造函数抛出了异常?
如果operator new抛出了异常,则没有任何内存被分配(抛出std:;bad_alloc),也就不应该调用operator delete,但是如果是类的构造函数中抛出异常,说明内存已经分配完毕,则我们就需要调用operator delete来执行清理操作。

指针比较的含义

在C++中,一个对象可以具有多个有效的地址,因此,指针比较不是地址的问题,而是对象同一性的问题。

C++中常见术语错误

一个比较常见的问题是,不同语言间对于实现相同行为的描述术语也都不相同。比如Java或者其他语言用方法(method)来描述类内的函数,而C++里是没有方法(method)这个概念的,应该称为成员函数(member function)
在C++中比较常出错的有以下几种术语:

WrongRight
Pure virtual base classAbstract class
MethodMember function
Virtual method???
DestructedDestroyed
Cast operatorConversion operator

为什么要有引用?

我觉得C++具有引用的有两个基本原因:

  1. 防止对象拷贝带来的开销(比指针的间接访问要更简洁)
  2. 对于IO流的使用(比如cout<<x<<y返回流的引用等价于使用cout<<x,cout<<y)

并发和并行(concurrency and parallel)

以下内容摘自CASPP,2e(其实用流描述并行和并发是依赖于操作系统实现的)
A logical flow whose execution overlaps in time with another flow is called a concurrent flow, and the two flows are said to run concurrently. More precisely, flows X and Y are concurrent with respect to each other if and only if X begins after Y begins and before Y finishes, or Y begins after X begins and before X finishes. For example, in Figure 8.12, processes A and B run concurrently, as do A and C. On the other hand, B and C do not run concurrently, because the last instruction of B executes before the first instruction of C.
The general phenomenon of multiple flows executing concurrently is known as concurrency. The notion of a process taking turns with other processes is also known as multitasking. Each time period that a process executes a portion of its flow is called a time slice. Thus, multitasking is also referred to as time slicing. For example, in Figure 8.12, the flow for Process A consists of two time slices.
Notice that the idea of concurrent flows is independent of the number of processor cores or computers that the flows are running on. If two flows overlap in time, then they are concurrent, even if they are running on the same processor. However, we will sometimes find it useful to identify a proper subset of concurrent flows known as parallel flows. If two flows are running concurrently on different processor cores or computers, then we say that they are parallel flows, that they are running in parallel, and have parallel execution.

C++一些语言特性被设计的原因

  • 名字空间就是针对不同库里使用相同的名字而提供的机制
  • 异常处理是为建立一种处理错误的公共模型提供了基础
  • 模板是为定义独立于具体类型的容器类和算法而提供的一种机制,其中的具体类型可以由用户或者其他的库提供
  • 构造函数和析构函数为对象的初始化和最后清理提供了一种公共模型
  • 抽象类提供了一种机制,借助于它可以独立地定义接口,与实际被接口的类无关
  • 运行时类型信息是为了寻回类型信息而提供的一种机制,因为当对象被传递给一个库再传递回来的时候,可能只携带着不够特殊(基类的)类型信息。

为什么socket的IP地址是一个结构?

一个IPv4的地址就是一个32位的无符号整数。但是经常通过一个in_addr这个结构来存储。
该结构定义在<netinet/in.h>头文件中:
The <netinet/in.h> header shall define the in_addr structure, which shall include at least the following member:

1
2
3
struct in_addr{
in_addr_t s_addr;
};

in_addr_t Equivalent to the type uint32_t as described in <inttypes.h>.

但是为什么要用一个结构来存放IP地址呢?

[CSAPP,2e]把一个标量地址存放在结构中,是socket接口早起实现的不幸产物。为IP地址定义一个标量类型应该更有意义,但是现在更改已经太迟了,因为已经有大量应用基于此了。

TCP/IP协议族

TCP/IP的协议族是一组不同的协议组合在一起构成的协议族(应用层协议(FTP/Telnet等)、运输层(TCP/UDP)、网络层(IP/ICMP/IGMP)、链路层(设备驱动程序及网络接口设备))。尽管通常称该协议族为TCP/IP,但TCP和IP只是其中的两种协议而已。

POSIX data types

[IEEE Std 1003.1™ 2008]The implementation shall support one or more programming environments in which the widths of blksize_t, pid_t, size_t, ssize_t, and suseconds_t are no greater than the width of type long.

MMU和COW

来看一下COW的一个实例:在child process中修改从parent process中获取到的变量触发COW然后对child process和parent process中的分别取地址,可能会获取到相同的结果,但是他们并不在位于一个物理地址。

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
int x=12;
printf("start before: x = %d\n",x);
setbuf(stdout,NULL);
pid_t child;
if((child=fork())<0){
printf("error!\n");
}else{
if(child>0){
printf("x initial value on parent scope is %d address is %x\n",x,&x);
printf("parent! process ID is %d\n",getpid());
x=1;
printf("x on parent scope is %d address is %x\n\n",x,&x);
}else{
printf("x initial value on child scope is %d address is %x\n",x,&x);
printf("child! process ID is %d\n",getpid());
// x=2;
printf("x on child scope is %d address is %x\n\n",x,&x);
}
}
return 0;
}

fork And COW Run Resault
看上面的运行结果图,在运行时可以看到在子进程中对x取地址和父进程中对x取地址得到的结果相同,简单直觉上这不符号COW的规则,子进程中在对x写入操作时应该与原来父进程的地址是不一样的。
原因就是&取得的是虚拟地址,不同进程的同一个虚拟地址被MMU映射到不同的物理地址。所以他们的虚拟地址虽然相同,但是他们实际上还是指向不同的物理地址的。
fork And COW