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


cmake:could not find any instance of Visual Studio.

在使用cmske生成时如果产生下列错误:

1
2
3
4
5
6
7
CMake Error at CMakeLists.txt:51 (project):
Generator
Visual Studio 15 2017 Win64
could not find any instance of Visual Studio.

Configuring incomplete, errors occurred!
See also "E:/protobuf/cmake/CMakeFiles/CMakeOutput.log“

检查VS安装的时候有没有安装编译器、生成工具和运行时分类下的适用于桌面的VC++2015.3 v14.00(v140)工具集

UE:编译目标的RuntimeLibrary类型

与普通的c++项目不一样,UE的Game工程是不能在项目属性中设置RuntimeLibrary的,这部分有UBT代劳替我们处理。
其相关的代码在UnrealBuildTool\Windows\VCToolchain.cs(UE_4.22)中:

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
void AppendCLArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// ...
// Specify the appropriate runtime library based on the platform and config.
if (CompileEnvironment.bUseStaticCRT)
{
if (CompileEnvironment.bUseDebugCRT)
{
Arguments.Add("/MTd");
}
else
{
Arguments.Add("/MT");
}
}
else
{
if (CompileEnvironment.bUseDebugCRT)
{
Arguments.Add("/MDd");
}
else
{
Arguments.Add("/MD");
}
}
// ...
}

*.target.cs中使用了bUseStaticCRT=true后(默认为false),编译Debug版本是/MTd,其他版本是MT
*.target.cs中不能直接控制bUseDebugCRT,但是可以控制bDebugBuildsActuallyUseDebugCRT(默认为false),它由我们在VS选的ConfigurationbDebugBuildsActuallyUseDebugCRT的值来控制。

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
// UEBuildTarget.cs

// GetCppConfiguration
static CppConfiguration GetCppConfiguration(UnrealTargetConfiguration Configuration)
{
switch (Configuration)
{
case UnrealTargetConfiguration.Debug:
return CppConfiguration.Debug;
case UnrealTargetConfiguration.DebugGame:
case UnrealTargetConfiguration.Development:
return CppConfiguration.Development;
case UnrealTargetConfiguration.Shipping:
return CppConfiguration.Shipping;
case UnrealTargetConfiguration.Test:
return CppConfiguration.Shipping;
default:
throw new BuildException("Unhandled target configuration");
}
}

// CreateCompileEnvironmentForProjectFiles
public CppCompileEnvironment CreateCompileEnvironmentForProjectFiles()
{
CppPlatform CppPlatform = UEBuildPlatform.GetBuildPlatform(Platform).DefaultCppPlatform;
CppConfiguration CppConfiguration = GetCppConfiguration(Configuration);

SourceFileMetadataCache MetadataCache = SourceFileMetadataCache.CreateHierarchy(ProjectFile);

CppCompileEnvironment GlobalCompileEnvironment = new CppCompileEnvironment(CppPlatform, CppConfiguration, Architecture, MetadataCache);
LinkEnvironment GlobalLinkEnvironment = new LinkEnvironment(GlobalCompileEnvironment.Platform, GlobalCompileEnvironment.Configuration, GlobalCompileEnvironment.Architecture);

UEToolChain TargetToolChain = CreateToolchain(CppPlatform);
SetupGlobalEnvironment(TargetToolChain, GlobalCompileEnvironment, GlobalLinkEnvironment);

return GlobalCompileEnvironment;
}

// SetupGlobalEnvironment
private void SetupGlobalEnvironment(UEToolChain ToolChain, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment)
{
// ...
GlobalCompileEnvironment.bUseDebugCRT = GlobalCompileEnvironment.Configuration == CppConfiguration.Debug && Rules.bDebugBuildsActuallyUseDebugCRT;
// ...
}

所以,总结上面的代码,只有当VS的编译选项选的是Debug,并且在*.Target.csbDebugBuildsActuallyUseDebugCRT=true的时候,bUseDebugCRT才为true

情况一

  • bUseStaticCRT=true;
  • 以及bDebugBuildsActuallyUseDebugCRT=true;
  • 以及VS中的Configuration为Debug

以上三个条件全部满足的时候,编译的Runtime Library为/MTd

情况二

  • bUseStaticCRT=false;(默认)
  • 以及bDebugBuildsActuallyUseDebugCRT=true;
  • 以及VS中的Configuration为Debug

这三个条件,编译的Runtime Library为/MDd

情况三

情况二基础上把下列条件改为:

  • 以及bDebugBuildsActuallyUseDebugCRT=false;(默认)

或者:

  • 以及VS中的Configuration不是Debug

则这种情况下,编译的Runtime Library都是/MD.

检查二进制文件的Runtime Library类型

在之前的笔记中写到过,在VS中编译C++项目时,可以指定生成目标的Runtime Library类型:MTMD
之前的笔记中也写到了使用dumpbin.exe来判断一个二进制文件的平台信息(x86/x64),现在有了一个类似的问题,怎么判断一个二进制文件的Runtime LibraryMT还是MD呢?
回答这个问题,依然要用dumpbin.exe这个工具,通过查看二进制文件依赖的DLL信息来判断它的Runtime Library类型,但是这个方法的缺点是没办法判断是Debug还是Release的,一般混用debug/release的链接库会连接错误。
使用下列命令:

1
dumpbin.exe BINARY_FILE /dependents | findstr dll

如果具有以下输出,则是MD的:

1
2
3
4
5
6
7
8
MSVCP140.dll
VCRUNTIME140.dll
api-ms-win-crt-runtime-l1-1-0.dll
api-ms-win-crt-math-l1-1-0.dll
api-ms-win-crt-stdio-l1-1-0.dll
api-ms-win-crt-locale-l1-1-0.dll
api-ms-win-crt-heap-l1-1-0.dll
KERNEL32.dll

之所以可以断定他是MD的,是因为这个二进制文件中依赖了CRT DLL.

修改Runtime library的类型为MT之后,再次测试:

1
KERNEL32.dll

只有一个KERNEL32.dll,不依赖任何其他的CRT DLL,所以他是MT的。

VS:C5083错误

1
2
3
4
5
error C5038: data member 'UTcpNetPeer::ConnectionState' will be initialized after data member 'UTcpNetPeer::OpenedTime'
error C5038: data member 'UTcpNetPeer::bReceivedHeader' will be initialized after data member 'UTcpNetPeer::Thread'
error C5038: data member 'UTcpNetPeer::TotalBytesSent' will be initialized after data member 'UTcpNetPeer::bRun'
error C5038: data member 'UTcpNetPeer::RecvMessageDataRemaining' will be initialized after data member 'UTcpNetPeer::ConnectionRetryTimes'
error C5038: data member 'UTcpNetPeer::ConnectionRetryTimes' will be initialized after data member 'UTcpNetPeer::ConnectingTime'

其实这个应该不算做错误,只是UE 4.22的警告等级比较高。
这个报错的意思是,在构造函数的定义里对数据成员的初始化的顺序与在声明中不一样,建议还是改掉,但是如果改动量太多,可以禁用掉C5083这个警告。

1
#pragma warning (disable:5038)

UE4:What is Cook?

虚幻引擎内部使用特定的格式存储游戏内的资产(uasset),但是在游戏发行的时候需要将这个虚幻的资产转换为各种平台的不同格式,因为各个平台使用自己的专有格式或者各个平台上具有性能更好的存储格式。将虚幻内部格式转换为平台特定格式的过程被称作Cook。
对于Windows而言,可以在编辑器中执行Cook Content for Windows,或者使用UnrealFrontEnd进行Cook或者Package。
也可以使用下列命令进行cook:

1
UE4Editor.exe <GameName or uproject> -run=cook -targetplatform=<Plat1>+<Plat2> [-cookonthefly] [-iterate] [-map=<Map1>+<Map2>]

VS中的属性宏

在VS中配置include或者链接lib的时候需要指定路径,但是绝对路径很方便,可以使用VS的属性宏来设置。
比较常用的有以下几个:

  • SolutionDir:解决方案的路径。
  • ProjectDir:项目的路径。
  • ProjectName:项目名。
  • Platform:平台(x86/x64等)
  • Configuration:配置(Debug/Release)
  • RuntimeLibrary:运行时的类型MT/MD
  • 也可以包含用户自己在系统中的创建的环境变量,如$(BOOST)等。

用法皆是$(VAR_NAME),如$(SolutionDir)
微软有个页面列出了VS中可用的属性宏:Common macros for MSBuild commands and properties
在VS中也可以通过Project Properties-Configuration Properties-C/C++-General-EditAdditional Include Direcories-Macro中找到支持的宏列表:

使用Win库但没有添加链接库的链接错误

在代码中使用了MessageBoxA,但是在链接时却产生了一个链接错误:

1
2
3
4
5
6
1>------ Build started: Project: ELogging, Configuration: Debug x64 ------
1> Creating library ..\..\Bin\x64\Debug\ELogging.lib and object ..\..\Bin\x64\Debug\ELogging.exp
1>Logging.obj : error LNK2019: unresolved external symbol __imp_MessageBoxA referenced in function "public: void __cdecl Logging::WriteMsgs(enum LoggingEnum,char *,int,char *)" ([email protected]@@[email protected]@[email protected])
1>..\..\Bin\x64\Debug\ELogging.dll : fatal error LNK1120: 1 unresolved externals
1>Done building project "ELogging.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 1 up-to-date, 0 skipped ==========

说的是在WriteMsgs中使用到的符号__imp_MessageBoxA没有定义。
这个符号是定义在user32.lib中的,在项目中添加上即可,原本以为win库的lib都是默认链接的,坑。

相关问题:junk.obj : error LNK2019: unresolved external symbol __imp_MessageBoxA referenced in function main

查看二进制文件是x64还是x86版本

给一个dll或者lib,怎么判断它是64位还是32位的呢?
可以使用VS工具链中的dumpbin查看可执行文件中的各个段,文件头中有machine的信息,如果没有装VS,可以在这里下载
然后使用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ dumpbin.exe /headers EXECUTABLE_FILE
# fox example
$ dumpbin.exe /headers UE4Launcher.exe
Microsoft (R) COFF/PE Dumper Version 14.00.24234.1
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file UE4Launcher.exe
PE signature found
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
8664 machine (x64)
7 number of sections
5D70B753 time date stamp Thu Sep 5 15:20:51 2019
0 file pointer to symbol table
0 number of symbols
F0 size of optional header
22 characteristics
Executable
Application can handle large (>2GB) addresses
// ...

后面的一堆可以不看,需要关注的重点就是machine这一项,对于64位的文件这里是x64,32位的就是x86

VS:引入外部Lib时的LINK1112链接错误

1
libboost_thread-vc140-mt-gd-1_62.lib(thread.obj) : fatal error LNK1112: module machine type 'x86' conflicts with target machine type 'x64'

该错误表示,当前编译的项目的目标机器是x64,但是项目中引用的libboost_thread-vc140-mt-gd-1_62.lib却是x86的,所以才会报这个错误。

VS:引入外部Lib时的LINK2038链接错误

1
2
3
4
5
6
7
8
9
10
11
12
13
2>Generating Code...
2>libboost_system-vc140-mt-gd-1_62.lib(error_code.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>libboost_date_time-vc140-mt-gd-1_62.lib(greg_month.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>libboost_filesystem-vc140-mt-gd-1_62.lib(path_traits.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>libboost_filesystem-vc140-mt-gd-1_62.lib(path.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>libboost_filesystem-vc140-mt-gd-1_62.lib(operations.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>libboost_filesystem-vc140-mt-gd-1_62.lib(codecvt_error_category.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>libboost_filesystem-vc140-mt-gd-1_62.lib(windows_file_codecvt.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>libboost_thread-vc140-mt-gd-1_62.lib(thread.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>libboost_thread-vc140-mt-gd-1_62.lib(tss_pe.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj
2>LINK : fatal error LNK1104: cannot open file 'libboost_date_time-vc140-mt-sgd-1_62.lib'
2>Done building project "ELogging.vcxproj" -- FAILED.
========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

这个错误的关键点在于,当前的项目编译的Rutime Library的类型是MTd_StaticDebug而依赖的链接库libboost_thread-vc140-mt-gd-1_62.libMDd_DynamicDebug,所以造成了链接时的不匹配:

1
error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MTd_StaticDebug' in ELogging.obj

知道了问题,那么解决这个问题的办法就是,使当前编译的项目的RuntimeLibrary类型和所有依赖的lib的RuntimeLibrary类型保持一致

修改方法为:Project Properties-Configuration-C/C++-Code Generation-Runtime Library:

这是个坑点,在引入外部的Lib的时候一定要看清楚项目所依赖的类型。

VS: 预编译的C1010错误

编译代码时在一个全部代码全部被注释的.cpp文件中报了下面一个错误:

1
fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source?

提示没有添加预编译头stdafx.h到文件,可以报错的文件包含即可,但是这里我又不需要让它包含。

那么可以进入Project Properties-C/C++-Precompiled Header,将Precompiled Header改为Not Using Precompiled Headers,重新编译即可,缺点就是不能使用预编译的加速了。

所谓头文件预编译,就是把一个工程(Project)中使用的一些标准头文件(如Windows.h、Afxwin.h)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。
预编译头文件通过编译stdafx.cpp生成,以工程名命名,由于预编译的头文件的后缀是pch,所以编译结果文件是ProjectName.pch
编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的(Project Properties-C/C++-Precompiled Header-Precompiled Header File)。
编译器认为,所有在指令#include "stdafx.h"前的代码都是预编译的,它跳过#include "stdafx.h"指令,使用ProjectName.pch编译这条指令之后的所有代码。 因此,所有的CPP实现文件第一条语句都是#include "stdafx.h"

UE4:添加宏定义

不同于普通的C++项目,可以在VS项目(Project-Properties-Configuration-C/C++-Preprocessor-Preprocessor Definitions)中或者直接在文件中使用#define(这样的方法在UE的项目中也可以用),但是UE的项目有自己的在项目中添加宏的方法。

方法就是在*.build.cs中添加:

1
Definitions.Add("GW_WITH_DEBUG=1");

然后在代码中就可以使用这个宏了:

1
2
3
4
5
6
7
8
9
10
11
12
13
// for example
void AVRPlayer::BeginPlay()
{
Super::BeginPlay();

#ifdef GW_WITH_DEBUG
#if GW_WITH_DEBUG
UKismetSystemLibrary::PrintString(this, TEXT("GW_WITH_DEBUG is defined"), true, true);
#else
UKismetSystemLibrary::PrintString(this, TEXT("GW_WITH_DEBUG is not defined"), true, true);
#endif
#endif
}

编译DLL时报错LNK1561: entry point must be defined

编译DLL时报下列错误:

1
LINK : fatal error LNK1561: entry point must be defined

在VS中项目右键Properties,在当前的编译配置下检查,配置类型是不是DLL:

以及Configuration Porperties-Linker-System-SubSystem项:

改完之后重新编译即可。

LoadLibrary faild

GetLastError获取错误代码:

  • 126:依赖的DLL找不到。
  • 127 :DLL找到了,但是DLL里需要的符号找不到,通常就是版本有问题。
  • 193:无效的DLL文件,请检查DLL文件是否正常以及x86/x64是否匹配。

自动以管理员权限启动bat

将依赖管理员权限启动的bat命令写到Admin之后即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
@echo off
cd /d "%~dp0"
cacls.exe "%SystemDrive%\System Volume Information" >nul 2>nul
if %errorlevel%==0 goto Admin
if exist "%temp%\getadmin.vbs" del /f /q "%temp%\getadmin.vbs"
echo Set RequestUAC = CreateObject^("Shell.Application"^)>"%temp%\getadmin.vbs"
echo RequestUAC.ShellExecute "%~s0","","","runas",1 >>"%temp%\getadmin.vbs"
echo WScript.Quit >>"%temp%\getadmin.vbs"
"%temp%\getadmin.vbs" /f
if exist "%temp%\getadmin.vbs" del /f /q "%temp%\getadmin.vbs"
exit

:Admin

CMD杀掉进程

在Linux中可以使用kill PID来干掉某一个进程。

在Cmd中可以使用taskkill来终止进程:

1
taskkill /f /t /im PROCESS_NAME.exe

如:

1
taskkill /f /t /im qq.exe

批量ping

有时候会有一堆的域名和ip需要进行ping测试,看是哪些ping不通,可以用下面的命令(将待检测的ip和域名都放到ip.txt中):

1
$ for /f %D in (ip.txt) do (ping %D -n 1 && echo %i>>ok.txt || echo %D >>no.txt)

UE:cpp中包含的第一个.h必须是自身类的

不然会有下列的错误:

1
C:\Users\imzlp\Documents\Unreal Projects\GWorld\Source\GWorld\Private\GWorldGameEngine.cpp(1): error : Expected GWorldGameEngine.h to be first header included.

UE4:修改游戏打包的进程名和Icon

打开项目设置(Project Settings)找到Project-Description下的ProjectName,修改这个参数为你想要的运行时创建的进程名(默认为UEGame)。

修改各个平台的Icon则在项目设置中找到Platforms下的各个平台,以Windows为例:

设置完之后会在项目目录下创建一个Build/Windows/Application.ico的文件。

UE4:SetActorHiddenInGame与SetVisibility的同步

对于所有逻辑都在服务器上跑的内容(不考虑本地先做效果以及延迟补偿)的情况下,使用UE的网络架构很方便,但是如果考虑到这些情况再直接用UE的同步(尤其是显示效果的同步)时会出现一些问题。

AActor::SetActorHiddenInGame

当对一个同步的Actor在服务器上调用SetActorHiddenInGame的时,该操作会同步到客户端。

1
2
3
4
5
6
7
8
void AActor::SetActorHiddenInGame( bool bNewHidden )
{
if (bHidden != bNewHidden)
{
bHidden = bNewHidden;
MarkComponentsRenderStateDirty();
}
}

Actor::bHidden的这个状态是同步的。

1
2
UPROPERTY(Interp, EditAnywhere, Category=Rendering, BlueprintReadOnly, replicated, meta=(DisplayName = "Actor Hidden In Game", SequencerTrackClass = "MovieSceneVisibilityTrack"))
uint8 bHidden:1;

其他的非服务器客户端在接收到变量同步的事件后会调用AActor::PostNetReceive函数:

Always called immediately after properties are received from the remote.

可以看到,在客户端接收bHidden更改的变量之后,会调用本地的AActor::SetActorHiddenInGame

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
void AActor::PostNetReceive()
{
if (!bNetCheckedInitialPhysicsState)
{
// Initially we need to sync the state regardless of whether bRepPhysics has "changed" since it may not currently match IsSimulatingPhysics().
SyncReplicatedPhysicsSimulation();
ActorReplication::SavedbRepPhysics = ReplicatedMovement.bRepPhysics;
bNetCheckedInitialPhysicsState = true;
}

ExchangeB( bHidden, ActorReplication::SavedbHidden );
Exchange ( Owner, ActorReplication::SavedOwner );

if (bHidden != ActorReplication::SavedbHidden)
{
SetActorHiddenInGame(ActorReplication::SavedbHidden);
}
if (Owner != ActorReplication::SavedOwner)
{
SetOwner(ActorReplication::SavedOwner);
}

if (Role != ActorReplication::SavedRole)
{
PostNetReceiveRole();
}
}

AActor::SetActorHiddenInGame中做了MarkComponentsRenderStateDirty()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void AActor::MarkComponentsRenderStateDirty()
{
for (UActorComponent* ActorComp : GetComponents())
{
if (ActorComp && ActorComp->IsRegistered())
{
ActorComp->MarkRenderStateDirty();
if (UChildActorComponent* ChildActorComponent = Cast<UChildActorComponent>(ActorComp))
{
if (ChildActorComponent->GetChildActor())
{
ChildActorComponent->GetChildActor()->MarkComponentsRenderStateDirty();
}
}
}
}
}

会把Actor上所有注册过的组件标记为不渲染,从而实现了服务器调用SetActorHiddenInGame会同步到其他客户端的目的。

USceneComponent::SetVisibility

当UE的USceneComponent被设置为Component Replications时,在服务器上调用SetVisibility会同步到客户端,内部的机制也是变量同步(相同的状态不会传递给其他的客户端)。

USceneComponent::SetVisibility的代码如下:

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
void USceneComponent::SetVisibility(const bool bNewVisibility, const USceneComponent::EVisibilityPropagation PropagateToChildren)
{
bool bRecurseChildren = (PropagateToChildren == EVisibilityPropagation::Propagate);
if ( bNewVisibility != bVisible )
{
bRecurseChildren = bRecurseChildren || (PropagateToChildren == EVisibilityPropagation::DirtyOnly);
bVisible = bNewVisibility;
OnVisibilityChanged();
}

const TArray<USceneComponent*>& AttachedChildren = GetAttachChildren();
if (bRecurseChildren && AttachedChildren.Num() > 0)
{
// fully traverse down the attachment tree
// we do it entirely inline here instead of recursing in case a primitivecomponent is a child of a non-primitivecomponent
TInlineComponentArray<USceneComponent*, NumInlinedActorComponents> ComponentStack;

// prime the pump
ComponentStack.Append(AttachedChildren);

while (ComponentStack.Num() > 0)
{
USceneComponent* const CurrentComp = ComponentStack.Pop(/*bAllowShrinking=*/ false);
if (CurrentComp)
{
ComponentStack.Append(CurrentComp->GetAttachChildren());

if (PropagateToChildren == EVisibilityPropagation::Propagate)
{
CurrentComp->SetVisibility(bNewVisibility, EVisibilityPropagation::NoPropagation);
}

// Render state must be dirtied if any parent component's visibility has changed. Since we can't easily track whether
// any parent in the hierarchy was dirtied, we have to mark dirty always.
CurrentComp->MarkRenderStateDirty();
}
}
}
}

经过检测之后它会将变量bVisible设置为新的状态,这个变量是设置为同步的:

1
2
3
4
5
6
7
8
9
10
/** Whether to completely draw the primitive; if false, the primitive is not drawn, does not cast a shadow. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing=OnRep_Visibility, Category = Rendering)
uint8 bVisible:1;

void USceneComponent::OnRep_Visibility(bool OldValue)
{
bool ReppedValue = bVisible;
bVisible = OldValue;
SetVisibility(ReppedValue);
}

所以对于标记为Component Replications的组件在服务器上调用SetVisibility时会同步让客户端的也显示。
因为这一点性质在做网络同步时,同步状态的组件和显示效果的组件一定要分割开。

USceneComponent::SetHiddenInGame

USceneComponent::SetHiddenInGame是不同步的。

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
// Runtime/Engine/Private/Components/SceneComponent.cpp
void USceneComponent::SetHiddenInGame(const bool bNewHiddenGame, const USceneComponent::EVisibilityPropagation PropagateToChildren)
{
bool bRecurseChildren = (PropagateToChildren == EVisibilityPropagation::Propagate);
if ( bNewHiddenGame != bHiddenInGame )
{
bRecurseChildren = bRecurseChildren || (PropagateToChildren == EVisibilityPropagation::DirtyOnly);
bHiddenInGame = bNewHiddenGame;
OnHiddenInGameChanged();
}

const TArray<USceneComponent*>& AttachedChildren = GetAttachChildren();
if (bRecurseChildren && AttachedChildren.Num() > 0)
{
// fully traverse down the attachment tree
// we do it entirely inline here instead of recursing in case a primitivecomponent is a child of a non-primitivecomponent
TInlineComponentArray<USceneComponent*, NumInlinedActorComponents> ComponentStack;

// prime the pump
ComponentStack.Append(AttachedChildren);

while (ComponentStack.Num() > 0)
{
USceneComponent* const CurrentComp = ComponentStack.Pop(/*bAllowShrinking=*/ false);
if (CurrentComp)
{
ComponentStack.Append(CurrentComp->GetAttachChildren());

if (PropagateToChildren == EVisibilityPropagation::Propagate)
{
CurrentComp->SetHiddenInGame(bNewHiddenGame, EVisibilityPropagation::NoPropagation);
}

// Render state must be dirtied if any parent component's visibility has changed. Since we can't easily track whether
// any parent in the hierarchy was dirtied, we have to mark dirty always.
CurrentComp->MarkRenderStateDirty();
}
}
}
}

AActoe::SetHiddenInGame类似的写法,不过区别在于USceneComponent::SetHiddenInGame设置的变量bHiddenInGame是不同步的。

1
2
UPROPERTY(Interp, EditAnywhere, BlueprintReadOnly, Category=Rendering, meta=(SequencerTrackClass = "MovieSceneVisibilityTrack"))
uint8 bHiddenInGame:1;

UE4:行为树Task的ReceiveTickAI与ReceiveTick的区别

调用代码在UBTTask_BlueprintBase::ExecuteTask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
EBTNodeResult::Type UBTTask_BlueprintBase::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
// fail when task doesn't react to execution (start or tick)
CurrentCallResult = (ReceiveExecuteImplementations != 0 || ReceiveTickImplementations != 0) ? EBTNodeResult::InProgress : EBTNodeResult::Failed;
bIsAborting = false;

if (ReceiveExecuteImplementations != FBTNodeBPImplementationHelper::NoImplementation)
{
bStoreFinishResult = true;

if (AIOwner != nullptr && (ReceiveExecuteImplementations & FBTNodeBPImplementationHelper::AISpecific))
{
ReceiveExecuteAI(AIOwner, AIOwner->GetPawn());
}
else if (ReceiveExecuteImplementations & FBTNodeBPImplementationHelper::Generic)
{
ReceiveExecute(ActorOwner);
}

bStoreFinishResult = false;
}

return CurrentCallResult;
}

可以看到ReceiveTickAIReceiveTick的区别是判断了AIOwner(AAIController*)是否存在,正常来说,运行了行为树AAIController::RunBehaviorTask,其Controller是肯定会存在的,可能只是为了区分参数吧。

UE4: Actor的Connection

AActor::GetNetConnection获取的是当前Actor的Owner的Connection(递归获取):

1
2
3
4
UNetConnection* AActor::GetNetConnection() const
{
return Owner ? Owner->GetNetConnection() : nullptr;
}

APawn::GetNetConnection重写了这个函数,获取的是当前Pawn的Controller的Connection:

1
2
3
4
5
6
7
8
9
class UNetConnection* APawn::GetNetConnection() const
{
// if have a controller, it has the net connection
if ( Controller )
{
return Controller->GetNetConnection();
}
return Super::GetNetConnection();
}

AController也是继承于Actor的,APlayerController中重写了这个函数:

1
2
3
4
5
UNetConnection* APlayerController::GetNetConnection() const
{
// A controller without a player has no "owner"
return (Player != NULL) ? NetConnection : NULL;
}

获取的是APlayerControllerNetConnection成员,该成员是在APlayerController::SetPlayer中被赋值为参数InPlayer的,UNetConnection继承自UPlayer

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
void APlayerController::SetPlayer( UPlayer* InPlayer )
{
check(InPlayer!=NULL);

const bool bIsSameLevel = InPlayer->PlayerController && (InPlayer->PlayerController->GetLevel() == GetLevel());
// Detach old player if it's in the same level.
if (bIsSameLevel)
{
InPlayer->PlayerController->Player = NULL;
}

// Set the viewport.
Player = InPlayer;
InPlayer->PlayerController = this;

// cap outgoing rate to max set by server
UNetDriver* Driver = GetWorld()->GetNetDriver();
if( (ClientCap>=2600) && Driver && Driver->ServerConnection )
{
Player->CurrentNetSpeed = Driver->ServerConnection->CurrentNetSpeed = FMath::Clamp( ClientCap, 1800, Driver->MaxClientRate );
}

// initializations only for local players
ULocalPlayer *LP = Cast<ULocalPlayer>(InPlayer);
if (LP != NULL)
{
// Clients need this marked as local (server already knew at construction time)
SetAsLocalPlayerController();
LP->InitOnlineSession();
InitInputSystem();
}
else
{
NetConnection = Cast<UNetConnection>(InPlayer);
if (NetConnection)
{
NetConnection->OwningActor = this;
}
}

UpdateStateInputComponents();

#if ENABLE_VISUAL_LOG
if (Role == ROLE_Authority && FVisualLogger::Get().IsRecordingOnServer())
{
OnServerStartedVisualLogger(true);
}
#endif

// notify script that we've been assigned a valid player
ReceivedPlayer();
}

UE4: uasset format

FPackageFileSummary

Tag

  • PACKAGE_FILE_TAG/PACKAGE_FILE_TAG_SWAPPED:定义在Runtime/Core/Public/UObject/ObjectVersion.h
1
2
#define PACKAGE_FILE_TAG      0x9E2A83C1
#define PACKAGE_FILE_TAG_SWAPPED 0xC1832A9E

这个MigicNumber可以打开uasset文件,文件头的前四个字节即是。

FileVersionUE4FileVersionLicenseeUE4
Engine/Source/Runtime/Core/Private/UObject/ObjectVersion.cpp:

1
2
3
// @see ObjectVersion.h for the list of changes/defines
const int32 GPackageFileUE4Version = VER_LATEST_ENGINE_UE4;
const int32 GPackageFileLicenseeUE4Version = VER_LATEST_ENGINE_LICENSEEUE4;

uasset的存储是写在FLinkerSave的构造函数中:

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
// Runtime/CoreUObject/Private/LinkerSave.cpp

/** A mapping of package name to generated script SHA keys */
TMap<FString, TArray<uint8> > FLinkerSave::PackagesToScriptSHAMap;

FLinkerSave::FLinkerSave(UPackage* InParent, const TCHAR* InFilename, bool bForceByteSwapping, bool bInSaveUnversioned)
: FLinker(ELinkerType::Save, InParent, InFilename )
, Saver(nullptr)
{
if (FPlatformProperties::HasEditorOnlyData())
{
// Create file saver.
Saver = IFileManager::Get().CreateFileWriter( InFilename, 0 );
if( !Saver )
{
UE_LOG(LogLinker, Fatal, TEXT("%s"), *FString::Printf( TEXT("Error opening file '%s'."), InFilename ) );
}

UPackage* Package = dynamic_cast<UPackage*>(LinkerRoot);

// Set main summary info.
Summary.Tag = PACKAGE_FILE_TAG;
Summary.SetFileVersions( GPackageFileUE4Version, GPackageFileLicenseeUE4Version, bInSaveUnversioned );
Summary.SavedByEngineVersion = FEngineVersion::Current();
Summary.CompatibleWithEngineVersion = FEngineVersion::CompatibleWith();
Summary.PackageFlags = Package ? (Package->GetPackageFlags() & ~PKG_NewlyCreated) : 0;

#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
Summary.LocalizationId = TextNamespaceUtil::GetPackageNamespace(LinkerRoot);
}
#endif // USE_STABLE_LOCALIZATION_KEYS

if (Package)
{
#if WITH_EDITORONLY_DATA
Summary.FolderName = Package->GetFolderName().ToString();
#endif
Summary.ChunkIDs = Package->GetChunkIDs();
}

// Set status info.
this->SetIsSaving(true);
this->SetIsPersistent(true);
ArForceByteSwapping = bForceByteSwapping;

#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
SetLocalizationNamespace(Summary.LocalizationId);
}
#endif // USE_STABLE_LOCALIZATION_KEYS
}
}

UE4:DECLARE_CLASS与Super

UE中的实现中使用了大量的宏,比如UObject::StaticClass的定义并没有直接写在Object.h中,而是使用DECLARE_CLASS宏来实现的:

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
// Runtime/CoreUObject/Public/UObject/ObjectMacros.h
/*-----------------------------------------------------------------------------
Class declaration macros.
-----------------------------------------------------------------------------*/

#define DECLARE_CLASS( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage, TRequiredAPI ) \
private: \
TClass& operator=(TClass&&); \
TClass& operator=(const TClass&); \
TRequiredAPI static UClass* GetPrivateStaticClass(); \
public: \
/** Bitwise union of #EClassFlags pertaining to this class.*/ \
enum {StaticClassFlags=TStaticFlags}; \
/** Typedef for the base class ({{ typedef-type }}) */ \
typedef TSuperClass Super;\
/** Typedef for {{ typedef-type }}. */ \
typedef TClass ThisClass;\
/** Returns a UClass object representing this class at runtime */ \
inline static UClass* StaticClass() \
{ \
return GetPrivateStaticClass(); \
} \
/** Returns the package this class belongs in */ \
inline static const TCHAR* StaticPackage() \
{ \
return TPackage; \
} \
/** Returns the static cast flags for this class */ \
inline static EClassCastFlags StaticClassCastFlags() \
{ \
return TStaticCastFlags; \
} \
/** For internal use only; use StaticConstructObject() to create new objects. */ \
inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags) \
{ \
return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags); \
} \
/** For internal use only; use StaticConstructObject() to create new objects. */ \
inline void* operator new( const size_t InSize, EInternal* InMem ) \
{ \
return (void*)InMem; \
}

该宏在UObject的声明中使用:

1
2
3
4
5
6
7
class COREUOBJECT_API UObject : public UObjectBaseUtility
{
// Declarations, normally created by UnrealHeaderTool boilerplate code
DECLARE_CLASS(UObject,UObject,CLASS_Abstract|CLASS_NoExport|CLASS_Intrinsic|CLASS_MatchedSerializers,CASTCLASS_None,TEXT("/Script/CoreUObject"),NO_API)

// ...
};

注意:这个宏在UHT生成的xxxx.generated.h中被使用,本质就是在继承后的类中也使用了DECLARE_CLass宏,并且把其中所有的typedef和函数都定义了一遍,由于name hidden,在当前类的namespace下使用typedef或者调用其中的函数,都是当前类定义的。
比如,ABaseActor继承自AActor,那么我在ABaseActor中使用Super,使用的是AAcotr::Super,而不是更深的继承层次的Super

修改Epub书籍的页面翻页方向

我们习惯的书籍是从左往右翻页的,但是在港台有些出版的书籍是从右往左翻页的,很不习惯。
今天我下了一本书就是这个样子的,研究了一下可以修改为从左往右的翻页方式。
首先把目标epub文件解压出来,找到content.opf文件,打开编辑,找到如下内容:

1
2
<spine page-progression-direction="rtl" toc="ncx">
</spine>

问题就是出在page-progression-direction="rtl"这里,从参数上可以看rtl就是right to left啊,改成ltr(left to right)后,把刚解压出来的所有文件压缩成zip文件然后把zip后缀改为epub即可。

无损转换AZW3到EPUB

使用Calibre默认的AZW3转换到EPUB中间是有损压缩的,可以安装Calibre的一个插件来实现无损转换。
在Calibre的首选项-插件下搜索KindleUnpack并安装,安装之后重启Calibre,可以看到工具栏有了一个KindleUnpack的按钮。

选中书籍后点击工具栏上的KindleUnpack按钮选择AZW3-UnpackAZW3

路由器级联:使两个路由器在同一网段

今天有个需求,需要把单独连接一个路由器A的几台电脑与连接其他路由器B的电脑配置在同一个局域网里。
方法为,关闭路由器A里的DHCP,从路由器B的LAN口拉一条网线过来查到路由器A的LAN口,其他的设备还接路由器A的LAN口,在路由器或者电脑的网络设置中设置过IP之后,路由器A就和路由器B连接的设备在同一个局域网下了。

UE4:GenerateProjectFiles.bat运行错误2

1
2
3
4
5
6
7
8
9
Setting up Unreal Engine 4 project files...
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin
\Microsoft.Common.CurrentVersion.targets(1179,5): error MSB3644: 未找到框架“.NETFram
ework,Version=v4.6.2”的引用程序集。若要解决此问题,请安装此框架版本的 SDKTargeting Pack,或将应用程序的目标重新指
向已装有 SDKTargeting Pack 的框架版本。请注意,将从全局程序集缓存(GAC)解析程序集,并将使用这些程序集替换引用程序集。因此,程序集
的目标可能未正确指向您所预期的框架。 [D:\UnrealEngine\Offical_Source\4.22\Engine\Source\Programs\
UnrealBuildTool\UnrealBuildTool.csproj]

GenerateProjectFiles ERROR: UnrealBuildTool failed to compile.

这是因为Engine\Source\Programs\ UnrealBuildTool\UnrealBuildTool.csproj中指定的.NetFramework版本与本地已经安装的不匹配。

首先,先查看本机安装的.NetFramework版本,可以打开注册表:

我的是528040,可以通过微软的网站查到不同的数字对应的版本号:如何:确定已安装的 .NET Framework 版本.

可以看到.NetFramework的版本号对照:

1
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full

找到Release项,其是一个REG_DWORD值。

.NET Framework 版本Release DWORD 的值
.NET Framework 4.5所有 Windows 操作系统:378389
.NET Framework 4.5.1在 Windows 8.1 和 Windows Server 2012 R2 上:378675
在所有其他 Windows 操作系统上:378758
.NET Framework 4.5.2所有 Windows 操作系统:379893
.NET Framework 4.6在 Windows 10 上:393295
在所有其他 Windows 操作系统上:393297
.NET Framework 4.6.1在 Windows 10 11 月更新系统上:394254
在所有其他 Windows 操作系统(包括 Windows 10)上:394271
.NET Framework 4.6.2在 Windows 10 周年更新和 Windows Server 2016 上:394802
在所有其他 Windows 操作系统(包括其他 Windows 10 操作系统)上:394806
.NET Framework 4.7在 Windows 10 创意者更新上:460798
在所有其他 Windows 操作系统(包括其他 Windows 10 操作系统)上:460805
.NET Framework 4.7.1在 Windows 10 Fall Creators Update 和 Windows Server 版本 1709 上:461308
在所有其他 Windows 操作系统(包括其他 Windows 10 操作系统)上:461310
.NET Framework 4.7.2在 Windows 10 2018 年 4 月更新和 Windows Server 版本 1803 上:461808
在除 Windows 10 2018 年 4 月更新和 Windows Server 版本 1803 之外的所有 Windows 操作系统上:461814
.NET Framework 4.8在 Windows 10 2019 年 5 月更新上:528040
在所有其他 Windows 操作系统(包括其他 Windows 10 操作系统)上:528049

通过这个对照表可以看到,我电脑上装的是.NET Framework 4.8,但是UBT的csproj文件里指定的是4.6.2,找不到对应的版本就产生了错误。
一个最简单的解决办法是打开visual studio install勾选上想要安装的.NetFramework版本安装即可。

UE4:GenerateProjectFiles.bat运行错误1

1
2
3
4
5
6
7
8
9
10
Setting up Unreal Engine 4 project files...
While compiling E:\UnrealEngine\EngineSource\UE_4.21_Source\Engine\Intermediate\Build\BuildRules\UE4Rules.dll:
e:\UnrealEngine\EngineSource\UE_4.21_Source\Engine\Plugins\Experimental\GeometryCache\Source\GeometryCache\GeometryCache.Build.cs(5,14) : error CS0101: 命名空间“<全局命名空间>”已经包含了“GeometryCache”的定义
e:\UnrealEngine\EngineSource\UE_4.21_Source\Engine\Plugins\Experimental\GeometryCache\Source\GeometryCacheEd\GeometryCacheEd.Build.cs(5,14) : error CS0101: 命名空间“<全局命名空间>”已经包含了“GeometryCacheEd”的定义
e:\UnrealEngine\EngineSource\UE_4.21_Source\Engine\Plugins\Runtime\HTNPlanner\Source\HTNPlanner\HTNPlanner.Build.cs(5,18) : error CS0101: 命名空间“UnrealBuildTool.Rules”已经包含了“HTNPlanner”的定义
e:\UnrealEngine\EngineSource\UE_4.21_Source\Engine\Plugins\Runtime\HTNPlanner\Source\HTNTestSuite\HTNTestSuite.Build.cs(7,18) : error CS0101: 命名空间“UnrealBuildTool.Rules”已经包含了“HTNTestSuite”的定义
e:\UnrealEngine\EngineSource\UE_4.21_Source\Engine\Plugins\Runtime\MixedRealityFramework\Source\ThirdParty\OpenCV\OpenCV.Build.cs(6,14) : error CS0101: 命名空间“<全局命名空间>”已经包含了“OpenCV”的定义
ERROR: UnrealBuildTool Exception: Unable to compile source files.

GenerateProjectFiles ERROR: UnrealBuildTool was unable to generate project files.

这是因为所提示的文件已经存在,将报错提示的文件删除之后再重新运行GenerateProjectFiles.bat即可。

相关问题:Build Error on GenerateProjectFiles

UE添加Profiling统计信息:代码块的CPU计数周期

在使用UnrealForntEnd来来抓取Profiling数据的时候,可以看到函数调用的开销信息,如果自己创建一个新的函数,则需要自己添加标记。可以使用DECLARE_CYCLE_STAT宏来声明(写在.cpp中):

1
DECLARE_CYCLE_STAT(TEXT("ProfilingDeclare"), STAT_ProfilingDeclare, STATGROUP_Profiling)

使用的方法为用SCOPE_CYCLE_COUNTER宏,并传入前面声明中的第二个参数ProfilingDeclare

1
2
3
4
5
6
7
8
void AExample::Func()
{
{
SOCPE_CYCLE_COUNTER(STAT_ProfilingDeclare);
// Something
}

}

VMWare扩展Linux虚拟机磁盘

首先在虚拟机启动之前(关闭状态下),调整虚拟机的磁盘大小:

经过漫长的等待...

与网上的其他方法不一样,我不打算列一堆命令,有一个最简单的方法。

那就是在虚拟机设置里添加一个CD/DVD的硬件,挂载GPrated的镜像,在选择CD-ROM加载之后,进去就是图形化的分区工具了,十分方便。

引导菜单:

启动之后:

Linux查看文件系统类型

查看文件系统类型可以使用以下命令:

1
$ df -Th

UE4:ON_SCOPE_EXIT

UE中有一个宏ON_SCOPE_EXIT,它的用法为:

1
2
3
4
5
// Make sure module info is added to known modules and proper delegates are fired on exit.
ON_SCOPE_EXIT
{
FModuleManager::Get().AddModuleToModulesList(InModuleName, ModuleInfo);
};

UE4 Modules:Find the DLL and load it里曾提到过这种用法:

这段代码的意思是在退出当前的作用域(Scope)时执行{}中的逻辑,简单地来说,它定义了一个当前作用域的对象并托管了一个Lambda,在离开当前作用域的时候通过C++的RAII机制来调用托管的Lambda.

今天来简单分析一下它的实现。

首先,ON_SCOPE_EXIT的宏定义为:

1
#define ON_SCOPE_EXIT const auto ANONYMOUS_VARIABLE(ScopeGuard_) = ::ScopeExitSupport::FScopeGuardSyntaxSupport() + [&]()

由此展开,最开始的那个使用的宏就等价替换为:

1
2
3
const auto ANONYMOUS_VARIABLE(ScopeGuard_) = ::ScopeExitSupport::FScopeGuardSyntaxSupport() + [&](){
FModuleManager::Get().AddModuleToModulesList(InModuleName, ModuleInfo);
};

通常lambda用在谓词和调用的时候还好理解,但是这个搞了个+有点不走寻常路,由此可以推断类型::ScopeExitSupport::FScopeGuardSyntaxSupport必然重载了operator+操作符,并且是个模板函数。

查看::ScopeExitSupport::FScopeGuardSyntaxSupport的代码:

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
// Runtime\Core\Public\Misc\ScopeExit.h
namespace ScopeExitSupport
{
/**
* Not meant for direct consumption : use ON_SCOPE_EXIT instead.
*
* RAII class that calls a lambda when it is destroyed.
*/
template <typename FuncType>
class TScopeGuard : public FNoncopyable
{
public:
// Given a lambda, constructs an RAII scope guard.
explicit TScopeGuard(FuncType&& InFunc)
: Func(MoveTemp(InFunc)){}

// This constructor needs to be available for the code to compile.
// It will be almost definitely be RVOed out (even in DEBUG).
TScopeGuard(TScopeGuard&& Other)
: Func(MoveTemp(Other.Func))
{
Other.Func.Reset();
}

// Causes
~TScopeGuard()
{
if (Func.IsSet())
{
Func.GetValue()();
}
}

private:
// The lambda to be executed when this guard goes out of scope.
TOptional<FuncType> Func;
};

struct FScopeGuardSyntaxSupport
{
template <typename FuncType>
TScopeGuard<FuncType> operator+(FuncType&& InFunc)
{
return TScopeGuard<FuncType>(Forward<FuncType>(InFunc));
}
};
}

果然!类型FScopeGuardSyntaxSupport通过模板重载了operator+,它要求接收一个重载了operator()的泛型函数对象(functional object)类型,据此产生一个TScopeGuard的类型,并将传递进来的函数对象托管,在这个TScopeGuard的对象的析构函数中(destructor)调用了托管的函数对象。

其中隐藏的关键是通过::ScopeExitSupport::FScopeGuardSyntaxSupport() + [&](){}得到的TScopeGuard的对象是个局部对象(automattic storage duration),只存在于当前的作用域(Scope)中,在离开当前作用域时会自动销毁(调用析构函数),离开作用域时局部变量的销毁顺序为按定义的逆序执行。

[ISO/IEC 14882:2014 § 6.6]On exit from a scope (however accomplished), objects with automatic storage duration (3.7.3) that have been constructed in that scope are destroyed in the reverse order of their construction.

C++局部对象的销毁顺序

C++标准规定按照构造的逆序执行销毁。

[ISO/IEC 14882:2014 § 6.6]On exit from a scope (however accomplished), objects with automatic storage duration (3.7.3) that have been constructed in that scope are destroyed in the reverse order of their construction.

UE4:Spawn特效被遮挡时在摄像机中不显示

遇到一个问题,当SpawnEmitterAttached一个特效时,如果该特效有被其他东西遮挡(但不是完全遮挡),在摄像机中会看不到,转到另一面就能看到,这个问题需要检查一下特效的Bounds的大小,应该是Bounds太小了被其他的东西完全挡着了,有时间再来详细分析一下这个问题。

History of Computer Animation

DirectX的资料文档

爆炸性旧新闻,全程高能:DirectX engineering specs published!
弥补了写的狗屎一般的DX12文档:
https://microsoft.github.io/DirectX-Specs/

龙书DirectX12 github地址:
https://github.com/d3dcoder/d3d12book

龙书DirectX12作者网址(中文版貌似没有配光盘,可以从该网站的DX11版资源中找到练习中缺失的图片等):
http://d3dcoder.net/

DX12微软github示例:
https://github.com/Microsoft/DirectX-Graphics-Samples

D3D文档(尽管DX12部分写的非常屎……):
https://docs.microsoft.com/en-us/windows/desktop/direct3d

DirectXMath库:
https://github.com/Microsoft/DirectXMath

DirectX texture library (DirectXTex):
https://github.com/Microsoft/DirectXTex

微软开源着色器编译器:
https://github.com/Microsoft/DirectXShaderCompiler

walbourn的github上也有一些好东东,如D3D工程模板等等:
https://github.com/walbourn

PIX博客与下载地址:
https://blogs.msdn.microsoft.com/pix/
https://blogs.msdn.microsoft.com/pix/download/

DirectX 12 DirectXTK12 (DirectX Tool Kit):
https://github.com/Microsoft/DirectXTK12

DirectXMesh geometry processing library:
https://github.com/Microsoft/DirectXMesh

DirectX Tool Kit使用示范:
https://github.com/walbourn/directxtk-samples

DX博客,最新消息:
https://blogs.msdn.microsoft.com/directx/

Direct3D feature levels discussion(特性级别讨论,比较细致):
https://forum.beyond3d.com/threads/direct3d-feature-levels-discussion.56575

围绕Win10 DirectX 11平台

DirectX 11.x DirectXTK(DirectX Tool Kit):
https://github.com/Microsoft/DirectXTK

Direct3D 11.x DXUT:
https://github.com/Microsoft/DXUT

Effects for Direct3D 11 (FX11):
https://github.com/Microsoft/FX11

RoadMap

Clayman《游戏程序员养成计划》:
http://www.cnblogs.com/clayman/archive/2009/05/17/1459001.html

Milo Yip《游戏程序员的学习之路(中英)》:
https://miloyip.github.io/game-programmer/

Chrome重启之后就暂停同步

打开 chrome://settings/content/cookies ,仅将本地数据保留到您退出浏览器为止取消勾选。重启 Chrome 即可。

UE:MODULE_NAME_API

UE中的模块,如果定义的符号允许被外部模块访问,则需要在class前加MODULE_NAME_API,这个宏是什么意思呢?
打开Definitions.MODULE_NAME.h这个文件可以知道,MODULE_NAME_API这个宏的定义是DLLEXPORT,看名字就知道是导出符号的,但是针对不同的平台是不一样的,DLLEXPORTDLLIMPORT的宏是定义在Runtime\Core\Public\${PLATFROM}\${PLATFROM}Platfrom.h下的(Android与Linux 的相同):

1
2
3
4
5
6
// Runtime\Core\Public\Android\AndroidPlatform.h
// Runtime\Core\Public\Linux\LinuxPlatform.h

// DLL export and import definitions
#define DLLEXPORT __attribute__((visibility("default")))
#define DLLIMPORT __attribute__((visibility("default")))

MacOS则是一个空的宏定义:

1
2
3
4
5
// Runtime\Core\Public\Mac\MacPlatform.h

// DLL export and import definitions
#define DLLEXPORT
#define DLLIMPORT

Windows的为__declspec

1
2
3
4
5
// Runtime\Core\Public\Windows\WIndowsPlatform.h

// DLL export and import definitions
#define DLLEXPORT __declspec(dllexport)
#define DLLIMPORT __declspec(dllimport)

UE:模块的宏定义

UE中模块的宏定义会汇总在这个文件下:

1
Intermediate\Build\Win64\UE4Editor\Development\NetExampleDemo\Definitions.MODULE_NAME.h

其中定义了MODULE_NAME_APIDLLEXPORT,导出自己模块内的符号,还有引用的其他模块的MODULE_NAME_APIDLLIMPORT,导入引用模块内的符号,在编译时链接可用,还有一些宏开关。
类似于这样:Definations.WebBrowserEx.h

Windows与MacOS双系统时间不一致的解决方案

因为MacOS读取时间为UTC时间,Windows直接读取BIOS时间为本地时间,所以在双系统时时间上会有8个小时的误差。
解决办法为,管理员权限打开CMD,执行下列命令:

1
Reg add HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation /v RealTimeIsUniversal /t REG_DWORD /d 1

UE4:Listen Server模式下的SkipOwner

如果在监听服务器(Listen Server)的模式下,作为服务端的玩家设置同步的变量,如果该变量的Replication ConditionalSkipOwner(此时监听服务器为该Actor的Owner),则该监听客户端的Rep_方法也会执行(因为它既是服务器也是客户端)。
即如果在使用SkipOwner用来处理类似本地模拟的事件时,LocallyControlledPlayer触发的事件先看到效果,其他客户端走变量同步触发Rep_的逻辑,但在这种机制下作为监听服务器的处理逻辑会走两遍。

隐藏bat的黑窗口

1
2
3
4
5
@echo off
if "%1" == "h" goto begin
mshta vbscript:createobject("wscript.shell").run("""%~nx0"" h",0)(window.close)&&exit
:begin
"%~dp0\Engine\Binaries\Win64\UE4Launcher.exe"

保存为.bat,将真正需要执行的命令写入begin之后即可。

解除MacOS安装软件的限制

  • 允许任何来源,运行第三方应用
1
sudo spctl --master-disable
  • 安装灰色的dmg
1
hdiutil attach #dmg文件名#

MacOS读写NTFS

装上macOS之后发现,macOS可以读取ntfs的文件,但是不可以写入,这十分蛋疼。
查了一下说是因为微软的限制,但是macOS本身是做了读写的功能的,只是被关闭了,可以通过以下方法开启。

首先,在终端下执行diskutil 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
27
28
visionsmile$ diskutil list
/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *240.1 GB disk0
1: EFI 312.5 MB disk0s1
2: Microsoft Reserved 134.2 MB disk0s2
3: Microsoft Basic Data Windows 164.3 GB disk0s3
4: Apple_APFS Container disk1 75.3 GB disk0s4

/dev/disk1 (synthesized):
#: TYPE NAME SIZE IDENTIFIER
0: APFS Container Scheme - +75.3 GB disk1
Physical Store disk0s4
1: APFS Volume OSX 34.2 GB disk1s1
2: APFS Volume Preboot 21.1 MB disk1s2
3: APFS Volume Recovery 509.8 MB disk1s3
4: APFS Volume VM 2.1 GB disk1s4

/dev/disk2 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *4.0 TB disk2
1: Microsoft Reserved 134.2 MB disk2s1
2: Microsoft Basic Data Document 4.0 TB disk2s2

/dev/disk3 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *1.0 TB disk3
1: Windows_NTFS Documents2 1.0 TB disk3s1

需要记录的是ntfs磁盘的NAME信息,我这里有三个ntfs分区windows/Documents/Documents2.

然后继续执行命令,更新fatab文件:

1
sudo nano /etc/fstab

输入下列内容,并把LABEL=之后的内容替换为上面记录的分区的名字:

1
2
3
LABEL=Windows none ntfs rw,auto,nobrowse
LABEL=Documents none ntfs rw,auto,nobrowse
LABEL=Documents2 none ntfs rw,auto,nobrowse

重启之后即可。
注意:重启之后的移动磁盘不会在桌面上现实,必须要打开Finder才可以看到。

UE4:SpawnSoundAtLocation失败的情况

UE中创建一个声音使用的是UGameplatStatic::SpawnSoundAtLocation,但是在项目中发现有时候它会被创建失败,看了一下代码,发现确实会存在创建失败的情况。
其调用栈如下:

FAudioDevice::LocationIsAudible中做了检测:

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
bool FAudioDevice::LocationIsAudible(const FVector& Location, const float MaxDistance) const
{
if (MaxDistance >= WORLD_MAX)
{
return true;
}

const bool bInAudioThread = IsInAudioThread();
const bool bInGameThread = IsInGameThread();

check(bInAudioThread || bInGameThread);

if (bInAudioThread)
{
for (const FListener& Listener : Listeners)
{
if (LocationIsAudible(Location, Listener.Transform, MaxDistance))
{
return true;
}
}
}
else //if (bInGameThread)
{
for (const FTransform& ListenerTransform : ListenerTransforms)
{
if (LocationIsAudible(Location, ListenerTransform, MaxDistance))
{
return true;
}
}
}

return false;
}
bool FAudioDevice::LocationIsAudible(const FVector& Location, const FTransform& ListenerTransform, float MaxDistance)
{
if (MaxDistance >= WORLD_MAX)
{
return true;
}

const float MaxDistanceSquared = MaxDistance * MaxDistance;
return (ListenerTransform.GetTranslation() - Location).SizeSquared() < MaxDistanceSquared;
}

通过检测Spawn的位置和声音的距离来判断这个创建的声音能不能被任何Listener听到,如果不能则不会创建。
ListenerUGameViewportClient::Draw中被设置:

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
// Update the listener.
if (AudioDevice != NULL && PlayerController != NULL)
{
bool bUpdateListenerPosition = true;

// If the main audio device is used for multiple PIE viewport clients, we only
// want to update the main audio device listener position if it is in focus
if (GEngine)
{
FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager();

// If there is more than one world referencing the main audio device
if (AudioDeviceManager->GetNumMainAudioDeviceWorlds() > 1)
{
uint32 MainAudioDeviceHandle = GEngine->GetAudioDeviceHandle();
if (AudioDevice->DeviceHandle == MainAudioDeviceHandle && !bHasAudioFocus)
{
bUpdateListenerPosition = false;
}
}
}

if (bUpdateListenerPosition)
{
FVector Location;
FVector ProjFront;
FVector ProjRight;
PlayerController->GetAudioListenerPosition(/*out*/ Location, /*out*/ ProjFront, /*out*/ ProjRight);

FTransform ListenerTransform(FRotationMatrix::MakeFromXY(ProjFront, ProjRight));

// Allow the HMD to adjust based on the head position of the player, as opposed to the view location
if (GEngine->XRSystem.IsValid() && GEngine->StereoRenderingDevice.IsValid() && GEngine->StereoRenderingDevice->IsStereoEnabled())
{
const FVector Offset = GEngine->XRSystem->GetAudioListenerOffset();
Location += ListenerTransform.TransformPositionNoScale(Offset);
}

ListenerTransform.SetTranslation(Location);
ListenerTransform.NormalizeRotation();

uint32 ViewportIndex = PlayerViewMap.Num() - 1;
AudioDevice->SetListener(MyWorld, ViewportIndex, ListenerTransform, (View->bCameraCut ? 0.f : MyWorld->GetDeltaSeconds()));
}
}

Listener的位置是通过PlayerController::GetAudioListenerPosition来获取的,而该函数获取的是AudioListenerComponent的位置和旋转,可以通过PlayerController::SetAudioListenerOverride来设置使用的组件。

PS:只有在当前的AudioDeviceMainAudioDevice并且不在焦点的时候才不会更新Listener的位置。

或许这个限制是由于性能优化的考虑,但是也十分坑爹,因为如果在最开始的时候(组件的BeginPlay)创建一个声音,可能挂载的Actor的位置是在原点的,不在任何Listener的范围内,则就会创建失败。

MBR无损迁移到GPT

之前装系统使用的是MBR分区表,但是最近要折腾一下黑苹果,四叶草只能UEFI启动,所以必须要是GPT分区表,但是完全不想完全格掉重装,折腾了一下,可以无损迁移。
首先,要有一个可以启动并且有GHOST工具的U盘。

  1. 进入PE,使用GHOST备份当前MBR的C盘
  2. 备份完成后,删除原C盘硬盘的所有分区,修改分区表为GPT并创建好分区
  3. 在PE中使用GHOST将备份的系统还原到GPT的分区中
  4. 引导原版的系统镜像(将boot/efi/bootmgrbootmgr.efi放入恢复完成的分区)
  5. 重启进入原版镜像的安装模式,使用修复系统会重建EFI的系统引导,修复完成即可。

注意:4-5两个步骤可以使用其他的引导修复工具替代。

类内默认值与构造函数初始化的顺序

在C++11中,引入了类内初始化机制:

1
2
3
4
5
6
7
8
9
class A{
public:
A(){}

private:
// initialized as 10
// or int ival{10};
int ival=10;
};

但是这又会引入一个问题:如果我同时使用类内初始化和构造函数初始化两种方式,那么真正使用的又是什么呢?

1
2
3
4
5
6
7
8
class A{
public:
A():ival(11){}

private:
// initialized as 10
int ival=10;
};

上面的代码ival的值应该是多少呢?
这一点在C++标准中给了明确的解答:

[IOS/IEC 14882:2014 §12.6.2.9]If a given non-static data member has both a brace-or-equal-initializer and a mem-initializer, the initialization specified by the mem-initializer is performed, and the non-static data member’s brace-or-equal-initializer is ignored.

1
2
3
4
5
struct A {
int i = /∗ some integer expression with side effects ∗/ ;
A(int arg) : i(arg) { }
// ...
};

the A(int) constructor will simply initialize i to the value of arg, and the side effects in i’s brace-or-equal-initializer will not take place.

但是我仍想自己分析一下编译器的实际处理,在之前的文章和笔记中,已经知道了编译器会将类内初始化的操作合并到构造函数中,其执行是在基类的构造之后,自己的构造函数体之前。依然使用LLVM-IR代码分析:

1
2
3
4
5
6
7
8
class A{
public:
A(){printf("");}

private:
// initialized as 10
int ival=10;
};

其构造函数的IR代码为:

1
2
3
4
5
6
7
8
9
10
; Function Attrs: noinline optnone uwtable
define linkonce_odr dso_local void @_ZN1AC2Ev(%class.A*) unnamed_addr #5 comdat align 2 {
%2 = alloca %class.A*, align 8
store %class.A* %0, %class.A** %2, align 8
%3 = load %class.A*, %class.A** %2, align 8
%4 = getelementptr inbounds %class.A, %class.A* %3, i32 0, i32 0
store i32 10, i32* %4, align 4
%5 = call i32 (i8*, ...) @_ZL6printfPKcz(i8* getelementptr inbounds ([1 x i8], [1 x i8]* @.str, i32 0, i32 0))
ret void
}

可以看到,是先初始化了this指针,然后根据this指针获取到成员ival,然后对其进行初始化(IR里的store操作)。
如果我们使用构造函数初始化的写法呢?

1
2
3
4
5
6
7
8
class A{
public:
A():ival(11){}

private:
// initialized as 10
int ival=10;
};

其IR代码为:

1
2
3
4
5
6
7
8
9
10
; Function Attrs: noinline optnone uwtable
define linkonce_odr dso_local void @_ZN1AC2Ev(%class.A*) unnamed_addr #5 comdat align 2 {
%2 = alloca %class.A*, align 8
store %class.A* %0, %class.A** %2, align 8
%3 = load %class.A*, %class.A** %2, align 8
%4 = getelementptr inbounds %class.A, %class.A* %3, i32 0, i32 0
store i32 10, i32* %4, align 4
%5 = call i32 (i8*, ...) @_ZL6printfPKcz(i8* getelementptr inbounds ([1 x i8], [1 x i8]* @.str, i32 0, i32 0))
ret void
}

通过diff发现,两个版本的IR代码唯一区别就是初始数值不一样,其他的逻辑都是一模一样的:

UE:注意RPC的调用不是严格顺序的

在UE中如果有两个连续调用的RPC事件A和B,其调用顺序到对端之后也不是完全保证就是一致的。
哪怕使用了Reliable来标记,只是标记其一定会到达,如果A先调用,然后调用B,但是A丢失了,重新发送A之后,顺序就是B先到达然后重新发送的A再到达。

Windows窗口切换快捷键

Win+Shift+Left/Right:可以控制窗口在多个显示器上左移或者右移;
Win+Shift+Up:保持窗口宽度的同时最大化窗口;
Win+Shift+Down:最小化窗口;

PS:Win+Shift+S则是屏幕截图。

UE项目跨大版本升级

尽量不要直接跨数个大版本升级项目,可能会存在资源的兼容性问题,最安全的做法是挨个大版本升级。

UE4:GC Config of BaseEngine.ini

可以打开ProjectSettings-Engine-GarbageCollection查看设置:

它们是在配置文件Engine\Config\BaseEngine.ini中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
; Engine\Config\BaseEngine.ini
[/Script/Engine.GarbageCollectionSettings]
gc.MaxObjectsNotConsideredByGC=0
gc.SizeOfPermanentObjectPool=0
gc.FlushStreamingOnGC=0
gc.NumRetriesBeforeForcingGC=0
gc.AllowParallelGC=True
gc.TimeBetweenPurgingPendingKillObjects=60
gc.MaxObjectsInEditor=8388607
gc.CreateGCClusters=True
gc.MergeGCClusters=False
gc.ActorClusteringEnabled=False
gc.BlueprintClusteringEnabled=False
gc.UseDisregardForGCOnDedicatedServers=False
  • AllowParallelGC:允许多线程执行GC
  • TimeBetweenPurgingPendingKillObjects:GC的清理周期默认为60s,可以通过调用:
    1
    GetWorld()->ForceGarbageCollection(true);

来强制GC。但是如果不想然一个UObject被GC回收,可以使用AddToRoot

  • CreateGCClusters:开启可以防止对很多子物体进行GC遍历。

Linux创建软链接

1
2
$ ln -s $TargetPath $SoftLinkPath
# ln -s ~/blog newblog

Linux添加PATH路径

Linux上可以通过export来查看当前的PATH路径列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ export
declare -x HOME="/root"
declare -x LANG="en_US.UTF-8"
declare -x LESSCLOSE="/usr/bin/lesspipe %s %s"
declare -x LESSOPEN="| /usr/bin/lesspipe %s"
declare -x LOGNAME="root"
declare -x LS_COLORS="rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"
declare -x MAIL="/var/mail/root"
declare -x OLDPWD
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/etc/ngrok_bin"
declare -x PWD="/root"
declare -x SHELL="/bin/bash"
declare -x SHLVL="1"
declare -x SSH_TTY="/dev/pts/0"
declare -x TERM="xterm"
declare -x USER="root"
declare -x XDG_DATA_DIRS="/usr/local/share:/usr/share:/var/lib/snapd/desktop"
declare -x XDG_RUNTIME_DIR="/run/user/0"
declare -x XDG_SESSION_ID="2"

或者:

1
$ echo $PATH

输出当前用户的PATH路径。

  1. 直接在终端中添加PATH,只在当前环境有效,退出终端失效:
1
$ export PATH=$PATH:ADD_PATH
  1. 为当前用户添加PATH路径,打开~/.profile增加以下内容:
1
export PATH=$PATH:ADD_PATH

重启之后生效。

变量模板的特化

求和:

1
2
3
4
5
6
7
8
9
10
template <int EndPoint>
constexpr int NUM = NUM<EndPoint-1> + EndPoint;

template<>
constexpr int NUM<0> = 0;

int main()
{
cout<<NUM<100>;
}

斐波那契第N项:

1
2
3
4
5
6
7
8
9
10
11
12
13
template <int EndPoint>
constexpr int Fibonacci = Fibonacci<EndPoint-1> + Fibonacci<EndPoint-2>;

template<>
constexpr int Fibonacci<1> = 1;

template<>
constexpr int Fibonacci<2> = 1;

int main()
{
cout<<Fibonacci<7>;
}

最大支持的参数取决于编译器的最大递归深度,Clang可以通过-ftemplate-depth=N来指定。

note: use -ftemplate-depth=N to increase recursive template instantiation depth

Windows查看程序的网络连接

首先在任务管理器中显示进程的PID,然后在cmd下执行命令:

1
netstat -ano|findstr PID

把PID换成你的进程ID即可。

What is the Object in C/C++?

首先先说C++中的对象(object)
在一些教材中,会刻意渲染C++中的对象就是面向对象(object-oriented),这里的对象是类对象(class Object),但是C++标准规定的Object并不是这么狭隘的概念,C++里占用存储空间即对象(函数除外。

[ISO/IEC 14882:2014 §1.8]An object is a region of storage. [ Note: A function is not an object, regardless of whether or not it occupies storage in the way that objects do.——end note ] An object is created by a definition (3.1), by a new-expression (5.3.4) or by the implementation (12.2) when needed.

有些人说C++里只有class object是对象,这是很狭隘的观点,如果只有class object才能叫对象,那么该怎么描述built-in type的实例?而且class本身也就是要提供和built-in type相同的操作方式,类型的实体统一叫做对象(Object)才是恰当的。

[ISO/IEC 14882:2014 §1.8]An object can have a name (Clause 3). An object has a storage duration (3.7) which influences its lifetime (3.8). An object has a type (3.9). The term object type refers to the type with which the object is created.

如果从面向对象(object-oriented)理论的封装继承多态这个角度来说,确实只有class object是这个语境下的对象,不过C++标准的对象模型是存储及对象(An object is a region of storage)。

C语言中的对象(object)概念与C++中类似,也是存储即对象

[ISO/IEC 9899:1999 §3.14]region of data storage in the execution environment, the contents of which can represent values

C++模板必须提供声明和定义的原因

因为在C++中使用模板会产生新的类型,而只有在当前使用模板的翻译单元会产生(因为编译器只会推导当前翻译单元的模板)。如果模板和声明和定义分离,使用模板时产生了一个符号(类型),则编译器就会去查找该符号的定义,那么问题来了,如果模板的定义在其他的翻译单元,编译器的手伸不到,怎么在一个翻译单元产生一个符号的声明,而让另一个翻译单元也同样产生一个相同符号的定义呢?是因为编译器直到使用模板的确切地点才能
知道用哪些模板实参来实例化它。结果就是会产生链接错误。
所以,模板必须要同时提供声明和定义,才会使模板推导出的符号声明和定义会一致生成(把模板当成内联代码更容易理解)。

C++历史上(C++03)曾经允许过模板的分离编译(使用export),但是这个特性在C++11之后就废除掉了,一是其局限性太大(并不像普通的提供声明-定义的分离式编译那样,编译时仍需要提供代码的实现),二是其太难实现了(只有一个编译器Comeau实现过)。

VS修改文件编码

选择Tools-Customize-Commands:

File下添加Advanced Save Options选项:

然后打开文本文件时就可以在VS的菜单File下看到Advanced Save Options选项了:

或者安装插件ForceUTF8(NoBOM).vsix)

PS:最好代码都使用Unicode编码,避免编码问题造成奇奇怪怪的编译错误。

C++模板特化实例的static成员

每个从模板类特化出的类模板特化实例都具有自己单独的static成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T>
struct A{
static T StaticMem;
};

template<typename T>
T A<T>::StaticMem;

int main()
{
A<int>::StaticMem=123;
A<float>::StaticMem=4.56;
A<char>::StaticMem='b';
}

[ISO/IEC 14882:2014 § 14.7]Each class template specialization instantiated from a template has its own copy of any static members.

apt-get update错误:无法验证签名

如果apt-get出现下列错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
[email protected]:~/downloads/onedrive# sudo apt-get update && sudo apt-get -y --allow-unauthenticated
Hit:1 http://security.ubuntu.com/ubuntu artful-security InRelease
Hit:2 http://archive.ubuntu.com/ubuntu artful InRelease
Hit:3 http://archive.ubuntu.com/ubuntu artful-updates InRelease
Hit:4 http://archive.ubuntu.com/ubuntu artful-backports InRelease
Get:5 https://netcologne.dl.sourceforge.net/project/d-apt d-apt InRelease [4,893 B]
Err:5 https://netcologne.dl.sourceforge.net/project/d-apt d-apt InRelease
The following signatures couldn't be verified because the public key is not available: NO_PUBKEY EBCF975E5BA24D5E
Reading package lists... Done
W: GPG error: https://netcologne.dl.sourceforge.net/project/d-apt d-apt InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY EBCF975E5BA24D5E
E: The repository 'https://netcologne.dl.sourceforge.net/project/d-apt d-apt InRelease' is not signed.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

如果不修复这个错误回在安装或升级软件包时出现问题,因为APT具有一组可信密钥,用于确定是否可以对软件包进行身份验证,从而决定是否可以在系统上安装软件包。
可以执行以下操作将缺失的密钥添加到APT密钥管理器:
上面的错误是告诉我们缺少EBCF975E5BA24D5E这个键,添加密钥使用以下命令:

1
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys EBCF975E5BA24D5E

执行完毕即可解决错误。

this不能用在函数默认参数的原因

注意:成员函数(this传递)实现是Implementation-define的。
在我之前的文章中写道过,C++的成员函数与普通的函数之间的区别是具有隐式的this指针:

1
2
3
4
5
6
void func(){}

class A{
public:
void func(){}
};

他们的区别是:

1
2
3
4
5
6
// ::func
// name mangling as void @_Z4funcv()
void func(){}
// A::func
// name mangling as void @_ZN1A4funcEv(%class.A* %6)
void func(A*){}

本质上来说,成员函数的调用就是将当前对象的地址作为实参传递给函数形参this
之所以不能够在成员函数的默认实参使用this则因为,在C++中函数参数的求值顺序是不一定的:

[ISO/IEC 14882:2014 §8.3.6.9]A default argument is evaluated each time the function is called with no argument for the corresponding parameter. The order of evaluation of function arguments is unspecified. Consequently, parameters of a function shall not be used in a default argument, even if they are not evaluated. Parameters of a function declared before a default argument are in scope and can hide namespace and class member names.

1
2
3
4
5
int a;
int f(int a, int b = a); // error: parameter a used as default argument
typedef int I;
int g(float I, int b = I(2)); // error: parameter I found
int h(int a, int b = sizeof(a)); // error, parameter a used in default argument

因为上面的分析我们已经知道了,this指针实际就是函数的形参,所以如果允许this作为函数形参的默认参数,则:

1
2
3
4
5
6
class A{
public:
void func(A* this_copy=this);
};
// 实现上等价于
void A_func(A* this,A* this_copy=this);

这样的写法就是一个形参的默认实参依赖于另一个形参,因为函数形参的传递是没有顺序的,所以不能在默认参数中使用this.
标准的规则是相通的,一个规则适用在这里,必然也会在另一个地方也会受到限制。

萃取数组的元素个数

1
2
3
4
5
6
7
8
9
10
11
#define ABSL_ARRAYSIZE(array) (sizeof(ArraySizeHelper(array)))

template<typename T,size_t N>
auto ArraySizeHelper(const T (&array)[N])->char (&)[N];

int main()
{
int array[123];
std::cout<<ABSL_ARRAYSIZE(array)<<std::endl;
}
// output: 123

Chrome浏览器由所属组织管理

解决办法,删除:

1
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome

下的EnabledPlugins文件夹,重启 Chrome 即可。

Bin2Hex

我写了一个小工具,把二进制文件转换为hex数据的,思路就是对二进制文件逐字节地读取然后写入一个字符串中:

下载bin2hex,用法如下:

1
2
# bin2hex.exe FileName
$ ./bin2hex.exe Icon.ico

会在当前目录下产生一个Icon_ico.h文件,记录着Icon.ico的二进制数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Icon_ico.h
// origin file: Icon.ico
static const unsigned char
Icon_ico_data[]=
{
0x00,0x00,0x01,0x00,0x0a,0x00,0x10,0x10,0x10,0x00,0x01,0x00,
0x04,0x00,0x28,0x01,0x00,0x00,0xa6,0x00,0x00,0x00,0x10,0x10,
0x00,0x00,0x01,0x00,0x08,0x00,0x68,0x05,0x00,0x00,0xce,0x01,
0x00,0x00,0x10,0x10,0x00,0x00,0x01,0x00,0x20,0x00,0x68,0x04,
0x00,0x00,0x36,0x07,0x00,0x00,0x20,0x20,0x10,0x00,0x01,0x00,
0x04,0x00,0xe8,0x02,0x00,0x00,0x9e,0x0b,0x00,0x00,0x20,0x20,
0x00,0x00,0x01,0x00,0x08,0x00,0xa8,0x08,0x00,0x00,0x86,0x0e,
0x00,0x00,0x20,0x20,0x00,0x00,0x01,0x00,0x20,0x00,0xa8,0x10,
// something...
};

在使用的时候,将这个字符数组内的数据以二进制模式写入文件即可恢复源文件(逐字节地写入文件):

1
2
3
4
5
6
7
8
9
10
11
// hex2bin.cpp
#include <stdio.h>
#include <stdlib.h>
#include "Icon_ico.h"

int main()
{
FILE* loadFP=fopen("Icon.ico","wb");
fwrite(Icon_ico_data,sizeof(Icon_ico_data),1,loadFP);
fclose(loadFP);
}

这种方式在写Console程序时,编译进去一些资源很有用。

C读写二进制文件

读:

1
2
3
4
5
6
FILE* fp=fopen(rFileName, "rb");
for(int FileItem=0;(fileItem=getc(fp))!=EOF;)
{
printf("%02x",fileItem);
}
fclose(fp);

写:

1
2
3
4
5
6
7
8
9
10
static const unsigned char
Data[]=
{
0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,
0x49,0x48,0x44,0x52,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,
// ...
};
FILE* loadFP=fopen(wFileName,"wb");
fwrite(Data,sizeof(Data),1,loadFP);
fclose(loadFP);

龙书DX12勘误

中文版P30,1.8练习的第5小题:
5.设k为标量,向量$u=(u_x,u_y,u_z)$。求证$\left| \left| k \right| \right|\left| \left| u \right| \right|$.
中文此处$\left| \left| k \right| \right|\left| \left| u \right| \right|$有错误,标量无法取模,英文原版为$\left| k \right|\left| \left| u \right| \right|$

左手坐标系和右手坐标系


左右手坐标系的转换只反转其中的一个轴即可(如果同时反转两个轴和不反转是一样的)。

GNU扩展:struct初始化[first ... last]

GNU的扩展支持以下这样一种结构初始化的写法(Designated-Inits):

1
2
#define ARRAY_NUM 10
struct { int ival;double dval; } DataList[ARRAY_NUM] ={ [0 ... ARRAY_NUM-1].ival = 2 };

这段代码的意思是对DataList内的所有元素的ival成员初始化为2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define ARRAY_NUM 5
int main(int argc,char* argv[])
{
struct { int ival;double dval; } DataList[ARRAY_NUM] ={ [0 ... ARRAY_NUM-1].ival = 2 };

for(int index=0;index<ARRAY_NUM;++index)
{
printf("%d\t",DataList[index].ival);
}
return 0;
}

// output
2 2 2 2 2

虽然说也可以使用一个循环赋值来实现:

1
2
3
4
5
6
7
8
#define ARRAY_NUM 5
int main(void)
{
struct { int ival;double dval; } DataList[ARRAY_NUM];

for(int index=0;index<ARRAY_NUM;++index)
DataList[index].ival=2;
}

但是比较二者的汇编代码:

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
###############################
##define ARRAY_NUM 5
#int main(void)
#{
# struct { int ival;double dval; } DataList[ARRAY_NUM];
#
# for(int index=0;index<ARRAY_NUM;++index)
# DataList[index].ival=2;
#}
###############################
.text
.def main;
.scl 2;
.type 32;
.endef
.globl main # -- Begin function main
.p2align 4, 0x90
main: # @main
.seh_proc main
# %bb.0:
pushq %rbp
.seh_pushreg 5
subq $144, %rsp
.seh_stackalloc 144
leaq 128(%rsp), %rbp
.seh_setframe 5, 128
.seh_endprologue
callq __main
movl $0, 12(%rbp)
movl $0, -84(%rbp)
.LBB0_1: # =>This Inner Loop Header: Depth=1
cmpl $5, -84(%rbp)
jge .LBB0_4
# %bb.2: # in Loop: Header=BB0_1 Depth=1
movslq -84(%rbp), %rax
shlq $4, %rax
leaq -80(%rbp), %rcx
addq %rax, %rcx
movl $2, (%rcx)
# %bb.3: # in Loop: Header=BB0_1 Depth=1
movl -84(%rbp), %eax
addl $1, %eax
movl %eax, -84(%rbp)
jmp .LBB0_1
.LBB0_4:
xorl %eax, %eax
addq $144, %rsp
popq %rbp
retq
.seh_handlerdata
.text
.seh_endproc
# -- End function
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
###############################
##define ARRAY_NUM 5
#int main(int argc,char* argv[])
#{
# struct { int ival;double dval; } DataList[ARRAY_NUM] ={ [0 ... ARRAY_NUM-1].ival = 2 };
#}
###############################
.text
.def main;
.scl 2;
.type 32;
.endef
.globl main # -- Begin function main
.p2align 4, 0x90
main: # @main
.seh_proc main
# %bb.0:
pushq %rbp
.seh_pushreg 5
subq $128, %rsp
.seh_stackalloc 128
leaq 128(%rsp), %rbp
.seh_setframe 5, 128
.seh_endprologue
callq __main
xorl %eax, %eax
movl $80, %ecx
movl %ecx, %r8d
leaq -80(%rbp), %rdx
movq %rdx, %rcx
movl %eax, %edx
movl %eax, -84(%rbp) # 4-byte Spill
callq memset
movl $2, -80(%rbp)
movl $2, -64(%rbp)
movl $2, -48(%rbp)
movl $2, -32(%rbp)
movl $2, -16(%rbp)
movl -84(%rbp), %eax # 4-byte Reload
addq $128, %rsp
popq %rbp
retq
.seh_handlerdata
.text
.seh_endproc
# -- End function

可以看到,直接初始化的效率更高,不过从“可读性”(毕竟它不是标准C)和“可移植”的角度看,还是写个循环赋值靠谱一点。

信号量的竞态条件

[CSAPP,2E,12.5.2]当有多个线程在等待同一个信号量的时候,不能够预测V操作要重启哪个线程。

static成员初始化可以访问private成员

C++的static成员初始化是可以访问类的私有成员的:

1
2
3
4
5
6
class process {
static process* run_chain;
static process* running;
};
process* process::running = get_main();
process* process::run_chain = running;

[ISO/IEC 14882:2014 §9.4.2]The static data member run_chain of class process is defined in global scope; the notation process ::run_chain specifies that the member run_chain is a member of class process and in the scope of class process. In the static data member definition, the initializer expression refers to the static data member running of class process. — end example ]

CMD命令:拷贝文件夹

windows下拷贝多级目录可以使用xcopy.

1
xcopy /y/i/s/e ..\..\..\Content\Slate UE4Launcher\Engine\Content\Slate

CMD命令:创建多级目录

1
md 123\456\789

tar.gz的压缩与解压缩

1
2
3
4
# 压缩
$ tar zcvf target.tar.gz target_dir/
# 解压缩
$ tar zxvf target.tar.gz

!!的用法

看到!的这样一个用法:

1
2
3
4
5
int main()
{
int ival=3;
printf("%d\n",!!ival);
}

作用是如果ival是0,则!!ival的值是0,ival非0,则结果为1.

C语言中的枚举就是整型

[ISO/IEC 9899:1999 §6.4.4.4.2]An identifier declared as an enumeration constant has type int.

[ISO/IEC 9899:1999 §6.7.2.2.3]The identifiers in an enumerator list are declared as constants that have type int and may appear wherever such are permitted.

注:C++与C不同,C++的枚举是单独的类型,详见[ISO/IEC 14882:2014 C.16 Clause7]。

C++中纯虚函数不能够提供定义

[ISO/IEC 14882:2014 §10.4]A function declaration cannot provide both a pure-specifier and a definition.

1
2
3
struct C {
virtual void f() = 0 { }; // ill-formed
};

使用Calibre转换繁体为简体

首先,将原始的繁体书籍(epub/mobi)导入Calibre,然后使用转换:

在弹出的界面中选择搜索&替换,加载一个文字替换规则:Calibre繁体转简体规则

然后点元数据选择输出格式以及文件信息,最终点确定即可开始转换。

从GooglePlay下载图书并去除DRM

Google图书看书不方便,试了一下怎么把购买过的图书从GooglePlay上下载下来。
首先,打开play.google.com/books,找到你购买过的书,点击下载:

这里并不会把书真的下载下来,而是会下载下来一个*.ACSM文件。
然后需要下载Adobe Digital Editions来打开它,安装之后,将*.ACSM文件拖入Adobe Digital Editions的窗口中,会弹出下列窗口:

请选择我想要在不使用ID的情况下对我的计算机授权,之后点下一步

授权
之后会自动下载书籍并且会在Adobe Digital Editions中打开,但是这些图书文件是使用DRM保护的,无法使用其他的阅读器打开。需要去除DRM才可以被其他的阅读器打开,需要使用的工具为Calibre和Calibre的去除DRM插件DeDRM_tools.
下载完Calibre之后安装,解压DeDRM_tools压缩包。
打开Calibre的首选项-插件:


点击从文件夹加载插件,选择DeDRM_plugin.zip

安装完成之后选择文件类型 插件,点自定义插件:

为ADE添加一个key:

然后重启Calibre.
最后将从ADE下载下来的具有DRM的图书文件拖入Calibre后即去除了DRM(Calibre Library里生成的图书文件).

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

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

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

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

正则匹配C函数声明

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

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

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

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很多都是泛黄的,影响观感,可以通过ImageMagick将图片处理为黑白的:

1
convert 0020.jpg -threshold 55% threshold0020.jpg

经过尝试,阈值选择在140(255*55%)附近时,分割效果较好。灰度值大于该阈值的像素全部变为255(白色),灰度值低于该阈值的像素全部变为0(黑色)。

  • 转为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)。

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即可。

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|[email protected],-28775|[email protected],-28756|[email protected],-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