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


UE4:联机游戏中AI不在视野被裁剪

在联机游戏如果怪物不在玩家的视野中,会被裁剪,其碰撞事件不会被触发,解决办法是将SkeletonMeshComponent的update骨架的配置改成always,就不会剪裁了。

萃取数组的元素个数

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

UE:未包含CoreUObject.h的错误

相关链接:How to modify these errors in plugin?thanks

UE:C4668 Error

如果在UE中出现类似这样的错误:

1
error C4668: '__GUNC__' is not defined as a prepeocessor macro, replicing with '0' for '#if/#elif'

解决方案是在*.Build.cs文件里加上bEnableUndefinedIdentifierWarnings=false;(默认为true).
在UBT的代码中:

1
2
3
// Source\Programs\UnrealBuildTool\Configuration\ModuleRules.cs
// Enable warnings for using undefined identifiers in #if expressions
public bool bEnableUndefinedIdentifierWarnings = true;

通过它来控制是否在编译参数中加入/we4668:

1
2
3
4
5
6
7
8
9
10
11
12
// Source\Programs\UnrealBuildTool\Platform\Windows\VCToolChain.cs
if(WindowsPlatform.bUseVCCompilerArgs && CompileEnvironment.bEnableUndefinedIdentifierWarnings)
{
if (CompileEnvironment.bUndefinedIdentifierWarningsAsErrors)
{
Arguments.Add("/we4668");
}
else
{
Arguments.Add("/w44668");
}
}

UE:Detach Error in Net

这两天同事碰到这样一个同步问题:客户端抓取某样道具,使用AttachToComponent将其抓到手中,在丢弃时使用DetachFromComponent脱离。
在单机模式下毫无问题,但是涉及到网络同步时(UE的网络架构),有下面这样一个问题(为了测试我写了一个最简单的例子,使用最基础的C/S架构,完全由服务端处理(客户端只负责发起RPC调用和接收变量的同步)):

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
LogOutputDevice: Error: === Handled ensure: ===
LogOutputDevice: Error: Ensure condition failed: !bRegistered || GetAttachParent()->GetAttachChildren().Contains(this) [File:D:\Build\++UE4+Release-4.18+Compile\Sync\Engine\Source\Runtime\Engine\Private\Components\SceneComponent.cpp] [Line: 2001]
LogOutputDevice: Error: Attempt to detach SceneComponent 'Cube' owned by 'Weapon_C_0' from AttachParent 'Scene' while not attached.
LogOutputDevice: Error: Stack:
LogOutputDevice: Error: [Callstack] 0x0000000006FB2786 UE4Editor-Core.dll!FWindowsPlatformStackWalk::StackWalkAndDump() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\core\private\windows\windowsplatformstackwalk.cpp:200]
LogOutputDevice: Error: [Callstack] 0x0000000006D5123A UE4Editor-Core.dll!FDebug::EnsureFailed() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\core\private\misc\assertionmacros.cpp:298]
LogOutputDevice: Error: [Callstack] 0x0000000006D6B906 UE4Editor-Core.dll!FDebug::OptionallyLogFormattedEnsureMessageReturningFalse() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\core\private\misc\assertionmacros.cpp:425]
LogOutputDevice: Error: [Callstack] 0x00000000DCB09BCF UE4Editor-Engine.dll!USceneComponent::DetachFromComponent() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\components\scenecomponent.cpp:2001]
LogOutputDevice: Error: [Callstack] 0x00000000DCB20A19 UE4Editor-Engine.dll!USceneComponent::K2_DetachFromComponent() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\components\scenecomponent.cpp:1987]
LogOutputDevice: Error: [Callstack] 0x00000000DC5615D0 UE4Editor-Engine.dll!USceneComponent::execK2_DetachFromComponent() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\classes\components\scenecomponent.h:105]
LogOutputDevice: Error: [Callstack] 0x0000000006695264 UE4Editor-CoreUObject.dll!UFunction::Invoke() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\coreuobject\private\uobject\class.cpp:4542]
LogOutputDevice: Error: [Callstack] 0x0000000006859B74 UE4Editor-CoreUObject.dll!UObject::CallFunction() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:732]
LogOutputDevice: Error: [Callstack] 0x000000000686FA41 UE4Editor-CoreUObject.dll!UObject::ProcessContextOpcode() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:2167]
LogOutputDevice: Error: [Callstack] 0x0000000006871B72 UE4Editor-CoreUObject.dll!UObject::ProcessInternal() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:954]
LogOutputDevice: Error: [Callstack] 0x0000000006695264 UE4Editor-CoreUObject.dll!UFunction::Invoke() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\coreuobject\private\uobject\class.cpp:4542]
LogOutputDevice: Error: [Callstack] 0x0000000006870F66 UE4Editor-CoreUObject.dll!UObject::ProcessEvent() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:1314]
LogOutputDevice: Error: [Callstack] 0x00000000DC61E64B UE4Editor-Engine.dll!AActor::ProcessEvent() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\actor.cpp:693]
LogOutputDevice: Error: [Callstack] 0x00000000DD35AEDF UE4Editor-Engine.dll!FRepLayout::CallRepNotifies() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\replayout.cpp:2407]
LogOutputDevice: Error: [Callstack] 0x00000000DCBBE6AB UE4Editor-Engine.dll!FObjectReplicator::CallRepNotifies() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\datareplication.cpp:1474]
LogOutputDevice: Error: [Callstack] 0x00000000DCBE84BF UE4Editor-Engine.dll!FObjectReplicator::PostReceivedBunch() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\datareplication.cpp:1032]
LogOutputDevice: Error: [Callstack] 0x00000000DCBEB549 UE4Editor-Engine.dll!UActorChannel::ProcessBunch() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\datachannel.cpp:2297]
LogOutputDevice: Error: [Callstack] 0x00000000DCBF902C UE4Editor-Engine.dll!UActorChannel::ReceivedBunch() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\datachannel.cpp:2139]
LogOutputDevice: Error: [Callstack] 0x00000000DCBFD75C UE4Editor-Engine.dll!UChannel::ReceivedSequencedBunch() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\datachannel.cpp:296]
LogOutputDevice: Error: [Callstack] 0x00000000DCBFC6AE UE4Editor-Engine.dll!UChannel::ReceivedNextBunch() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\datachannel.cpp:665]
LogOutputDevice: Error: [Callstack] 0x00000000DCBFD4C5 UE4Editor-Engine.dll!UChannel::ReceivedRawBunch() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\datachannel.cpp:388]
LogOutputDevice: Error: [Callstack] 0x00000000DD0E23A6 UE4Editor-Engine.dll!UNetConnection::ReceivedPacket() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\netconnection.cpp:1319]
LogOutputDevice: Error: [Callstack] 0x00000000DD0E3602 UE4Editor-Engine.dll!UNetConnection::ReceivedRawPacket() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\netconnection.cpp:728]
LogOutputDevice: Error: [Callstack] 0x00000000D6F1A2E3 UE4Editor-OnlineSubsystemUtils.dll!UIpNetDriver::TickDispatch() [d:\build\++ue4+release-4.18+compile\sync\engine\plugins\online\onlinesubsystemutils\source\onlinesubsystemutils\private\ipnetdriver.cpp:232]
LogOutputDevice: Error: [Callstack] 0x00000000DD0C065F UE4Editor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float)>::ExecuteIfSafe() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\core\public\delegates\delegateinstancesimpl.h:858]
LogOutputDevice: Error: [Callstack] 0x00000000DC51CF78 UE4Editor-Engine.dll!TBaseMulticastDelegate<void,float>::Broadcast() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\core\public\delegates\delegatesignatureimpl.inl:937]
LogOutputDevice: Error: [Callstack] 0x00000000DCF653F8 UE4Editor-Engine.dll!UWorld::Tick() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\engine\private\leveltick.cpp:1296]
LogOutputDevice: Error: [Callstack] 0x00000000FE7D6BA6 UE4Editor-UnrealEd.dll!UEditorEngine::Tick() [d:\build\++ue4+release-4.18+compile\sync\engine\source\editor\unrealed\private\editorengine.cpp:1659]
LogOutputDevice: Error: [Callstack] 0x00000000FF084AC6 UE4Editor-UnrealEd.dll!UUnrealEdEngine::Tick() [d:\build\++ue4+release-4.18+compile\sync\engine\source\editor\unrealed\private\unrealedengine.cpp:396]
LogOutputDevice: Error: [Callstack] 0x0000000035025A26 UE4Editor.exe!FEngineLoop::Tick() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\launch\private\launchengineloop.cpp:3296]
LogOutputDevice: Error: [Callstack] 0x0000000035035430 UE4Editor.exe!GuardedMain() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\launch\private\launch.cpp:166]
LogOutputDevice: Error: [Callstack] 0x00000000350354AA UE4Editor.exe!GuardedMainWrapper() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\launch\private\windows\launchwindows.cpp:134]
LogOutputDevice: Error: [Callstack] 0x0000000035042379 UE4Editor.exe!WinMain() [d:\build\++ue4+release-4.18+compile\sync\engine\source\runtime\launch\private\windows\launchwindows.cpp:210]
LogOutputDevice: Error: [Callstack] 0x0000000035043D57 UE4Editor.exe!__scrt_common_main_seh() [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:253]
LogOutputDevice: Error: [Callstack] 0x0000000045863DC4 KERNEL32.DLL!UnknownFunction []
LogOutputDevice: Error: [Callstack] 0x0000000045D63691 ntdll.dll!UnknownFunction []

这是由于Attch组件自身和Attach ParentComponent Replication被设置了,然后在从Server收到的变量同步事件OnRep_中再做Detach操作就会触发。
其实问题一句话可以概括:将自己从AttachParent上Detach掉,会判断将要Detach父级的AttachChildren列表中判断自己存不存在,这个断言触发的是,自己的父级存在(Parent!=nullptr),但是父级对象的AttachChildren中不包含自己。
其实在C/S架构中,C端应该只做表现,而不处理逻辑,出现上面的问题也是联机模式下的实现思路问题,因为UE的Attach和Detach是会同步的,具体细节先按下不表,后续我会从代码分析一下这个问题。

Chrome浏览器由所属组织管理

解决办法,删除:

1
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome

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

UE Package: Create Compressed Cooked Packages

打包时压缩pak:

UE:GConfig的加载

UE中定义了一堆的全局ini:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Runtime/Core/Private/Misc/CoreGlobals.cpp
FString GEngineIni; /* Engine ini filename */

/** Editor ini file locations - stored per engine version (shared across all projects). Migrated between versions on first run. */
FString GEditorIni; /* Editor ini filename */
FString GEditorKeyBindingsIni; /* Editor Key Bindings ini file */
FString GEditorLayoutIni; /* Editor UI Layout ini filename */
FString GEditorSettingsIni; /* Editor Settings ini filename */

/** Editor per-project ini files - stored per project. */
FString GEditorPerProjectIni; /* Editor User Settings ini filename */

FString GCompatIni;
FString GLightmassIni; /* Lightmass settings ini filename */
FString GScalabilityIni; /* Scalability settings ini filename */
FString GHardwareIni; /* Hardware ini filename */
FString GInputIni; /* Input ini filename */
FString GGameIni; /* Game ini filename */
FString GGameUserSettingsIni; /* User Game Settings ini filename */

但是并没有直接硬编码指定ini是在哪里的,其加载的过程为:在FEngineLoop::AppInit(LaunchEngineLoop.cpp)中通过调用FConfigCacheIni::InitializeConfigSystem来执行加载ini文件,其定义在ConfigCacheIni.cpp

FConfigCacheIni::InitializeConfigSystem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// --------------------------------
// # FConfigCacheIni::InitializeConfigSystem declaration
/**
* Creates GConfig, loads the standard global ini files (Engine, Editor, etc),
* fills out GEngineIni, etc. and marks GConfig as ready for use
*/
static void InitializeConfigSystem();
// --------------------------------
void FConfigCacheIni::InitializeConfigSystem()
{
// Perform any upgrade we need before we load any configuration files
FConfigManifest::UpgradeFromPreviousVersions();

// create GConfig
GConfig = new FConfigCacheIni(EConfigCacheType::DiskBacked);

// load the main .ini files (unless we're running a program or a gameless UE4Editor.exe, DefaultEngine.ini is required).
const bool bIsGamelessExe = !FApp::HasProjectName();
const bool bDefaultEngineIniRequired = !bIsGamelessExe && (GIsGameAgnosticExe || FApp::IsProjectNameEmpty());
bool bEngineConfigCreated = FConfigCacheIni::LoadGlobalIniFile(GEngineIni, TEXT("Engine"), nullptr, bDefaultEngineIniRequired);

if ( !bIsGamelessExe )
{
// Now check and see if our game is correct if this is a game agnostic binary
if (GIsGameAgnosticExe && !bEngineConfigCreated)
{
const FText AbsolutePath = FText::FromString( IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FPaths::GetPath(GEngineIni)) );
//@todo this is too early to localize
const FText Message = FText::Format( NSLOCTEXT("Core", "FirstCmdArgMustBeGameName", "'{0}' must exist and contain a DefaultEngine.ini."), AbsolutePath );
if (!GIsBuildMachine)
{
FMessageDialog::Open(EAppMsgType::Ok, Message);
}
FApp::SetProjectName(TEXT("")); // this disables part of the crash reporter to avoid writing log files to a bogus directory
if (!GIsBuildMachine)
{
exit(1);
}
UE_LOG(LogInit, Fatal,TEXT("%s"), *Message.ToString());
}
}

FConfigCacheIni::LoadGlobalIniFile(GGameIni, TEXT("Game"));
FConfigCacheIni::LoadGlobalIniFile(GInputIni, TEXT("Input"));
#if WITH_EDITOR
// load some editor specific .ini files

FConfigCacheIni::LoadGlobalIniFile(GEditorIni, TEXT("Editor"));

// Upgrade editor user settings before loading the editor per project user settings
FConfigManifest::MigrateEditorUserSettings();
FConfigCacheIni::LoadGlobalIniFile(GEditorPerProjectIni, TEXT("EditorPerProjectUserSettings"));

// Project agnostic editor ini files
static const FString EditorSettingsDir = FPaths::Combine(*FPaths::GameAgnosticSavedDir(), TEXT("Config")) + TEXT("/");
FConfigCacheIni::LoadGlobalIniFile(GEditorSettingsIni, TEXT("EditorSettings"), nullptr, false, false, true, *EditorSettingsDir);
FConfigCacheIni::LoadGlobalIniFile(GEditorLayoutIni, TEXT("EditorLayout"), nullptr, false, false, true, *EditorSettingsDir);
FConfigCacheIni::LoadGlobalIniFile(GEditorKeyBindingsIni, TEXT("EditorKeyBindings"), nullptr, false, false, true, *EditorSettingsDir);

#endif
#if PLATFORM_DESKTOP
// load some desktop only .ini files
FConfigCacheIni::LoadGlobalIniFile(GCompatIni, TEXT("Compat"));
FConfigCacheIni::LoadGlobalIniFile(GLightmassIni, TEXT("Lightmass"));
#endif

// Load scalability settings.
FConfigCacheIni::LoadGlobalIniFile(GScalabilityIni, TEXT("Scalability"));
// Load driver blacklist
FConfigCacheIni::LoadGlobalIniFile(GHardwareIni, TEXT("Hardware"));

// Load user game settings .ini, allowing merging. This also updates the user .ini if necessary.
FConfigCacheIni::LoadGlobalIniFile(GGameUserSettingsIni, TEXT("GameUserSettings"));

// now we can make use of GConfig
GConfig->bIsReadyForUse = true;
FCoreDelegates::ConfigReadyForUse.Broadcast();
}

FConfigCacheIni::LoadGlobalIniFile

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
// --------------------------------
// # FConfigCacheIni::LoadGlobalIniFile declaration
/**
* Loads and generates a destination ini file and adds it to GConfig:
* - Looking on commandline for override source/dest .ini filenames
* - Generating the name for the engine to refer to the ini
* - Loading a source .ini file hierarchy
* - Filling out an FConfigFile
* - Save the generated ini
* - Adds the FConfigFile to GConfig
*
* @param FinalIniFilename The output name of the generated .ini file (in Game\Saved\Config)
* @param BaseIniName The "base" ini name, with no extension (ie, Engine, Game, etc)
* @param Platform The platform to load the .ini for (if NULL, uses current)
* @param bForceReload If true, the destination .in will be regenerated from the source, otherwise this will only process if the dest isn't in GConfig
* @param bRequireDefaultIni If true, the Default*.ini file is required to exist when generating the final ini file.
* @param bAllowGeneratedIniWhenCooked If true, the engine will attempt to load the generated/user INI file when loading cooked games
* @param GeneratedConfigDir The location where generated config files are made.
* @return true if the final ini was created successfully.
*/
static bool LoadGlobalIniFile(FString& FinalIniFilename, const TCHAR* BaseIniName, const TCHAR* Platform=NULL, bool bForceReload=false, bool bRequireDefaultIni=false, bool bAllowGeneratedIniWhenCooked=true, const TCHAR* GeneratedConfigDir = *FPaths::GeneratedConfigDir());
// --------------------------------

bool FConfigCacheIni::LoadGlobalIniFile(FString& FinalIniFilename, const TCHAR* BaseIniName, const TCHAR* Platform, bool bForceReload, bool bRequireDefaultIni, bool bAllowGeneratedIniWhenCooked, const TCHAR* GeneratedConfigDir)
{
// figure out where the end ini file is
FinalIniFilename = GetDestIniFilename(BaseIniName, Platform, GeneratedConfigDir);

// Start the loading process for the remote config file when appropriate
if (FRemoteConfig::Get()->ShouldReadRemoteFile(*FinalIniFilename))
{
FRemoteConfig::Get()->Read(*FinalIniFilename, BaseIniName);
}

FRemoteConfigAsyncIOInfo* RemoteInfo = FRemoteConfig::Get()->FindConfig(*FinalIniFilename);
if (RemoteInfo && (!RemoteInfo->bWasProcessed || !FRemoteConfig::Get()->IsFinished(*FinalIniFilename)))
{
// Defer processing this remote config file to until it has finish its IO operation
return false;
}

// need to check to see if the file already exists in the GConfigManager's cache
// if it does exist then we are done, nothing else to do
if (!bForceReload && GConfig->FindConfigFile(*FinalIniFilename) != nullptr)
{
//UE_LOG(LogConfig, Log, TEXT( "Request to load a config file that was already loaded: %s" ), GeneratedIniFile );
return true;
}

// make a new entry in GConfig (overwriting what's already there)
FConfigFile& NewConfigFile = GConfig->Add(FinalIniFilename, FConfigFile());

return LoadExternalIniFile(NewConfigFile, BaseIniName, *FPaths::EngineConfigDir(), *FPaths::SourceConfigDir(), true, Platform, bForceReload, true, bAllowGeneratedIniWhenCooked, GeneratedConfigDir);
}

FConfigCacheIni::LoadLocalIniFile

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
// --------------------------------
// # FConfigCacheIni::LoadLocalIniFile declaration
/**
* Load an ini file directly into an FConfigFile, and nothing is written to GConfig or disk.
* The passed in .ini name can be a "base" (Engine, Game) which will be modified by platform and/or commandline override,
* or it can be a full ini filenname (ie WrangleContent) loaded from the Source config directory
*
* @param ConfigFile The output object to fill
* @param IniName Either a Base ini name (Engine) or a full ini name (WrangleContent). NO PATH OR EXTENSION SHOULD BE USED!
* @param bIsBaseIniName true if IniName is a Base name, which can be overridden on commandline, etc.
* @param Platform The platform to use for Base ini names, NULL means to use the current platform
* @param bForceReload force reload the ini file from disk this is required if you make changes to the ini file not using the config system as the hierarchy cache will not be updated in this case
* @return true if the ini file was loaded successfully
*/
static bool LoadLocalIniFile(FConfigFile& ConfigFile, const TCHAR* IniName, bool bIsBaseIniName, const TCHAR* Platform=NULL, bool bForceReload=false);
// --------------------------------
bool FConfigCacheIni::LoadLocalIniFile(FConfigFile& ConfigFile, const TCHAR* IniName, bool bIsBaseIniName, const TCHAR* Platform, bool bForceReload )
{
DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FConfigCacheIni::LoadLocalIniFile" ), STAT_FConfigCacheIni_LoadLocalIniFile, STATGROUP_LoadTime );

FString EngineConfigDir = FPaths::EngineConfigDir();
FString SourceConfigDir = FPaths::SourceConfigDir();

if (bIsBaseIniName)
{
FConfigFile* BaseConfig = GConfig->FindConfigFileWithBaseName(IniName);
// If base ini, try to use an existing GConfig file to set the config directories instead of assuming defaults

if (BaseConfig)
{
FIniFilename* EngineFilename = BaseConfig->SourceIniHierarchy.Find(EConfigFileHierarchy::EngineDirBase);
if (EngineFilename)
{
EngineConfigDir = FPaths::GetPath(EngineFilename->Filename) + TEXT("/");
}

FIniFilename* GameFilename = BaseConfig->SourceIniHierarchy.Find(EConfigFileHierarchy::GameDirDefault);
if (GameFilename)
{
SourceConfigDir = FPaths::GetPath(GameFilename->Filename) + TEXT("/");
}
}

}

return LoadExternalIniFile(ConfigFile, IniName, *EngineConfigDir, *SourceConfigDir, bIsBaseIniName, Platform, bForceReload, false);
}

FConfigCacheIni::LoadExternalIniFile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// --------------------------------
// # FConfigCacheIni::LoadExternalIniFile declaration
/**
* Load an ini file directly into an FConfigFile from the specified config folders, optionally writing to disk.
* The passed in .ini name can be a "base" (Engine, Game) which will be modified by platform and/or commandline override,
* or it can be a full ini filenname (ie WrangleContent) loaded from the Source config directory
*
* @param ConfigFile The output object to fill
* @param IniName Either a Base ini name (Engine) or a full ini name (WrangleContent). NO PATH OR EXTENSION SHOULD BE USED!
* @param EngineConfigDir Engine config directory.
* @param SourceConfigDir Game config directory.
* @param bIsBaseIniName true if IniName is a Base name, which can be overridden on commandline, etc.
* @param Platform The platform to use for Base ini names
* @param bForceReload force reload the ini file from disk this is required if you make changes to the ini file not using the config system as the hierarchy cache will not be updated in this case
* @param bWriteDestIni write out a destination ini file to the Saved folder, only valid if bIsBaseIniName is true
* @param bAllowGeneratedIniWhenCooked If true, the engine will attempt to load the generated/user INI file when loading cooked games
* @param GeneratedConfigDir The location where generated config files are made.
* @return true if the ini file was loaded successfully
*/
static bool LoadExternalIniFile(FConfigFile& ConfigFile, const TCHAR* IniName, const TCHAR* EngineConfigDir, const TCHAR* SourceConfigDir, bool bIsBaseIniName, const TCHAR* Platform=NULL, bool bForceReload=false, bool bWriteDestIni=false, bool bAllowGeneratedIniWhenCooked = true, const TCHAR* GeneratedConfigDir = *FPaths::GeneratedConfigDir());
// --------------------------------
bool FConfigCacheIni::LoadExternalIniFile(FConfigFile& ConfigFile, const TCHAR* IniName, const TCHAR* EngineConfigDir, const TCHAR* SourceConfigDir, bool bIsBaseIniName, const TCHAR* Platform, bool bForceReload, bool bWriteDestIni, bool bAllowGeneratedIniWhenCooked, const TCHAR* GeneratedConfigDir)
{
// if bIsBaseIniName is false, that means the .ini is a ready-to-go .ini file, and just needs to be loaded into the FConfigFile
if (!bIsBaseIniName)
{
// generate path to the .ini file (not a Default ini, IniName is the complete name of the file, without path)
FString SourceIniFilename = FString::Printf(TEXT("%s/%s.ini"), SourceConfigDir, IniName);

// load the .ini file straight up
LoadAnIniFile(*SourceIniFilename, ConfigFile);

ConfigFile.Name = IniName;
}
else
{
FString DestIniFilename = GetDestIniFilename(IniName, Platform, GeneratedConfigDir);

GetSourceIniHierarchyFilenames( IniName, Platform, EngineConfigDir, SourceConfigDir, ConfigFile.SourceIniHierarchy, false );

if ( bForceReload )
{
ClearHierarchyCache( IniName );
}

// Keep a record of the original settings
ConfigFile.SourceConfigFile = new FConfigFile();

// now generate and make sure it's up to date (using IniName as a Base for an ini filename)
const bool bAllowGeneratedINIs = true;
bool bNeedsWrite = GenerateDestIniFile(ConfigFile, DestIniFilename, ConfigFile.SourceIniHierarchy, bAllowGeneratedIniWhenCooked, true);

ConfigFile.Name = IniName;

// don't write anything to disk in cooked builds - we will always use re-generated INI files anyway.
if (bWriteDestIni && (!FPlatformProperties::RequiresCookedData() || bAllowGeneratedIniWhenCooked)
// We shouldn't save config files when in multiprocess mode,
// otherwise we get file contention in XGE shader builds.
&& !FParse::Param(FCommandLine::Get(), TEXT("Multiprocess")))
{
// Check the config system for any changes made to defaults and propagate through to the saved.
ConfigFile.ProcessSourceAndCheckAgainstBackup();

if (bNeedsWrite)
{
// if it was dirtied during the above function, save it out now
ConfigFile.Write(DestIniFilename);
}
}
}

// GenerateDestIniFile returns true if nothing is loaded, so check if we actually loaded something
return ConfigFile.Num() > 0;
}

GetDestIniFilename

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
// Runtime/Source/Core/Private/Misc/ConfigCacheIni.cpp

/**
* Calculates the name of a dest (generated) .ini file for a given base (ie Engine, Game, etc)
*
* @param IniBaseName Base name of the .ini (Engine, Game)
* @param PlatformName Name of the platform to get the .ini path for (nullptr means to use the current platform)
* @param GeneratedConfigDir The base folder that will contain the generated config files.
*
* @return Standardized .ini filename
*/
static FString GetDestIniFilename(const TCHAR* BaseIniName, const TCHAR* PlatformName, const TCHAR* GeneratedConfigDir)
{
// figure out what to look for on the commandline for an override
FString CommandLineSwitch = FString::Printf(TEXT("%sINI="), BaseIniName);

// if it's not found on the commandline, then generate it
FString IniFilename;
if (FParse::Value(FCommandLine::Get(), *CommandLineSwitch, IniFilename) == false)
{
FString Name(PlatformName ? PlatformName : ANSI_TO_TCHAR(FPlatformProperties::PlatformName()));

FString BaseIniNameString = BaseIniName;
if (BaseIniNameString.Contains(GeneratedConfigDir))
{
IniFilename = BaseIniNameString;
}
else
{
// put it all together
IniFilename = FString::Printf(TEXT("%s%s/%s.ini"), GeneratedConfigDir, *Name, BaseIniName);
}
}

// standardize it!
FPaths::MakeStandardFilename(IniFilename);
return IniFilename;
}

FPaths::GeneratedConfigDir获取的路径是当前项目的Saved下的Config:

1
2
3
4
5
6
7
8
9
// Paths.cpp
FString FPaths::GeneratedConfigDir()
{
#if PLATFORM_MAC
return FPlatformProcess::UserPreferencesDir();
#else
return FPaths::ProjectSavedDir() + TEXT("Config/");
#endif
}

即在Windows平台上项目启动时创建的全局G*Ini文件读取的都是Saved/Config/Windows下的.ini
而且在GetDestIniFilename的实现中也可以看到,G*Ini的配置也是可以从CommandLine传入的,可以替换掉默认的Saved/Config/Platform下的ini:

1
2
// 使用指定的Engine.ini
UE4Editor.exe uprojectPath -EngineINI="D:\\CustomEngine.ini"


UE Package:Couldn't save package,filename is too long

在打包的时候遇到这个错误,是因为某些资源在Windows上的绝对路径长度超过了260个字符,这是NTFS的限制。
所以,将项目路径移动到磁盘根目录,或者减少目录层级即可。
引擎中相关的代码在:

1
2
3
4
5
6
7
8
9
10
11
// Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp(UE4.18.3)
// need to subtract 32 because the SavePackage code creates temporary files with longer file names then the one we provide
// projects may ignore this restriction if desired
const int32 CompressedPackageFileLengthRequirement = bConsiderCompressedPackageFileLengthRequirements ? 32 : 0;
const FString FullFilename = FPaths::ConvertRelativePathToFull(PlatFilename);
if (FullFilename.Len() >= (PLATFORM_MAX_FILEPATH_LENGTH - CompressedPackageFileLengthRequirement))
{
LogCookerMessage(FString::Printf(TEXT("Couldn't save package, filename is too long: %s"), *PlatFilename), EMessageSeverity::Error);
UE_LOG(LogCook, Error, TEXT("Couldn't save package, filename is too long :%s"), *PlatFilename);
Result = ESavePackageResult::Error;
}

而各个平台的PLATFORM_MAX_FILEPATH_LENGTH均定义在Runtime/Core/Public相关的平台下。
Windows是使用的另一个宏WINDOWS_MAX_PATH

1
2
3
4
5
6
D:\UnrealEngine\Offical_Source\4.18\Engine\Source\Runtime\Core\Public\Windows\WIndowsPlatform.h:
57 #define PLATFORM_HAS_BSD_TIME 0
58 #define PLATFORM_USE_PTHREADS 0
59: #define PLATFORM_MAX_FILEPATH_LENGTH WINDOWS_MAX_PATH
60 #define PLATFORM_HAS_BSD_SOCKET_FEATURE_WINSOCKETS 1
61 #define PLATFORM_USES_MICROSOFT_LIBC_FUNCTIONS 1

WINDOWS_MAX_PATH这个宏定义在Runtime/Core/Private/Windows/MinimalWindowsApi.h中:

1
#define WINDOWS_MAX_PATH 260

显然UE并没有使用Windows提供的MAX_PATH宏,硬编码为了260,即在UE中Windows上的文件路径最大长度为260个字符(在启用bConsiderCompressedPackageFileLengthRequirements时还要减去32个字符),虽然Windows10要移除260字符的限制,~但是貌似UE没有跟进的打算~(UE4.22 Released支持了长文件名的特性(实验性))。
bConsiderCompressedPackageFileLengthRequirements选项可以加在Saved/Config/Windows/Engine.ini下(默认为true):

1
2
[CookSettings]
bConsiderCompressedPackageFileLengthRequirements = true

这个配置是在Cook时会被加载用来判断是否考虑引擎生成的文件名长度:

1
2
3
4
5
6
7
8
// Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp(UE4.18.3)
// need to subtract 32 because the SavePackage code creates temporary files with longer file names then the one we provide
bool UCookOnTheFlyServer::ShouldConsiderCompressedPackageFileLengthRequirements() const
{
bool bConsiderCompressedPackageFileLengthRequirements = true;
GConfig->GetBool(TEXT("CookSettings"), TEXT("bConsiderCompressedPackageFileLengthRequirements"), bConsiderCompressedPackageFileLengthRequirements, GEditorIni);
return bConsiderCompressedPackageFileLengthRequirements;
}

UCookOnTheFlyServer::SaveCookedPackage中需要通过它来决定将系统支持的MAX_PATH减去32之后的结果,用来判断资源路径是否超出限制。

外部阅读:

UE开启多重采样和前向渲染

ProjectSetting-Rendering-ForwardRenderer/DefaultSetting-Anti-Aliasing Method

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);

UGameInstance::HandleNetworkError

当网络链接出现错误时,会调用该函数。
调用栈为(可以继承UEngine来替换Engine实现):

1
UEngine::HandleNetworkError -> UEngine::HandleNetworkFailure_NotifyGameInstance -> GameInstance->HandleNetworkError(FailureType, bIsServer);

其实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// Runtime\Source\Runtime\Engine\Private\UnrealEngine.cpp
void UEngine::HandleNetworkFailure(UWorld *World, UNetDriver *NetDriver, ENetworkFailure::Type FailureType, const FString& ErrorString)
{
UE_LOG(LogNet, Log, TEXT("NetworkFailure: %s, Error: '%s'"), ENetworkFailure::ToString(FailureType), *ErrorString);

if (!NetDriver)
{
return;
}

// Only handle failure at this level for game or pending net drivers.
FName NetDriverName = NetDriver->NetDriverName;
if (NetDriverName == NAME_GameNetDriver || NetDriverName == NAME_PendingNetDriver)
{
// If this net driver has already been unregistered with this world, then don't handle it.
if (World)
{
if (!FindNamedNetDriver(World, NetDriverName))
{
// This netdriver has already been destroyed (probably waiting for GC)
return;
}
}

// Give the GameInstance a chance to handle the failure.
HandleNetworkFailure_NotifyGameInstance(World, NetDriver, FailureType);

ENetMode FailureNetMode = NetDriver->GetNetMode(); // NetMode of the driver that failed
bool bShouldTravel = true;

switch (FailureType)
{
case ENetworkFailure::FailureReceived:
break;
case ENetworkFailure::PendingConnectionFailure:
// TODO stop the connecting movie
break;
case ENetworkFailure::ConnectionLost:
// Hosts don't travel when clients disconnect
bShouldTravel = (FailureNetMode == NM_Client);
break;
case ENetworkFailure::ConnectionTimeout:
// Hosts don't travel when clients disconnect
bShouldTravel = (FailureNetMode == NM_Client);
break;
case ENetworkFailure::NetGuidMismatch:
case ENetworkFailure::NetChecksumMismatch:
// Hosts don't travel when clients have actor issues
bShouldTravel = (FailureNetMode == NM_Client);
break;
case ENetworkFailure::NetDriverAlreadyExists:
case ENetworkFailure::NetDriverCreateFailure:
case ENetworkFailure::OutdatedClient:
case ENetworkFailure::OutdatedServer:
default:
break;
}

if (bShouldTravel)
{
CallHandleDisconnectForFailure(World, NetDriver);
}
}
}

void UEngine::HandleNetworkFailure_NotifyGameInstance(UWorld *World, UNetDriver *NetDriver, ENetworkFailure::Type FailureType)
{
bool bIsServer = true;

if (NetDriver != nullptr)
{
bIsServer = NetDriver->GetNetMode() != NM_Client;
}

if (World != nullptr && World->GetGameInstance() != nullptr)
{
World->GetGameInstance()->HandleNetworkError(FailureType, bIsServer);
}
else
{
// Since the UWorld passed in might be null, as well as the NetDriver's UWorld,
// go through the world contexts until we find the one with this net driver.
for (auto& Context : WorldList)
{
if (Context.PendingNetGame != nullptr &&
Context.PendingNetGame->NetDriver == NetDriver &&
Context.OwningGameInstance != nullptr)
{
// Use the GameInstance from the current context.
Context.OwningGameInstance->HandleNetworkError(FailureType, bIsServer);
}
}
}
}

ENetworkFailure的声明:

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
// Runtime\Engine\Classes\Engine\EngineBaseType.h
/** Types of network failures broadcast from the engine */
UENUM(BlueprintType)
namespace ENetworkFailure
{
enum Type
{
/** A relevant net driver has already been created for this service */
NetDriverAlreadyExists,
/** The net driver creation failed */
NetDriverCreateFailure,
/** The net driver failed its Listen() call */
NetDriverListenFailure,
/** A connection to the net driver has been lost */
ConnectionLost,
/** A connection to the net driver has timed out */
ConnectionTimeout,
/** The net driver received an NMT_Failure message */
FailureReceived,
/** The client needs to upgrade their game */
OutdatedClient,
/** The server needs to upgrade their game */
OutdatedServer,
/** There was an error during connection to the game */
PendingConnectionFailure,
/** NetGuid mismatch */
NetGuidMismatch,
/** Network checksum mismatch */
NetChecksumMismatch
};
}

龙书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|$

UE:Primary Asset Id

先在Project Setting-Game-Asset Manager里添加资源路径:

然后就可以在蓝图的`PrimaryAssetId中选择了:

左手坐标系和右手坐标系


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

UE4 Plugin:ModuleType(Required)

.uplugin Module Type Descriptors.

Sets the type of Module. Valid options are Runtime, RuntimeNoCommandlet, Developer, Editor, EditorNoCommandlet, and Program. This type determines which types of applications this Plugin's Module is suitable for loading in. For example, some plugins may include modules that are only designed to be loaded when the editor is running. Runtime modules will be loaded in all cases, even in shipped games. Developer modules will only be loaded in development runtime or editor builds, but never in shipping builds. Editor modules will only be loaded when the editor is starting up. Your Plugin can use a combination of modules of different types.

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
// Engine\Programs\UnrealBuildTool\System\ModuleDescriptor.cs
// The type of host that can load a module
public enum ModuleHostType
{
Default,

// Any target using the UE4 runtime
Runtime,

// Any target except for commandlet
RuntimeNoCommandlet,

// Any target or program
RuntimeAndProgram,

// Loaded only in cooked builds
CookedOnly,

// Loaded only when the engine has support for developer tools enabled
Developer,

// Loaded only by the editor
Editor,

// Loaded only by the editor, except when running commandlets
EditorNoCommandlet,

// Loaded only by programs
Program,

// Loaded only by servers
ServerOnly,

// Loaded only by clients
ClientOnly,
}

UE4 Plugin:LoadingPhase(Optional)

.uplugin Module LoadingPhase Descriptors.

If specified, controls when the plugin is loaded at start-up. This is an advanced option that should not normally be required. The valid options are Default(which is used when no LoadingPhase is specified), PreDefault, and PostConfigInit. PostConfigInit enables the module to be loaded before the engine has finished starting up key subsystems. PreDefault loads just before the normal phase. Typically, this is only needed if you expect game modules to depend directly on content within your plugin, or types declared within the plugin's code.

如果.upugin中没有指定LoadingPhase项,则默认是Default:

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
// Engine\Programs\UnrealBuildTool\System\ModuleDescriptor.cs
// Indicates when the engine should attempt to load this module
public enum ModuleLoadingPhase
{
/// Loaded at the default loading point during startup (during engine init, after game modules are loaded.)
Default,

// Right after the default phase
PostDefault,

// Right before the default phase
PreDefault,

// Loaded before the engine is fully initialized, immediately after the config system has been initialized. Necessary only for very low-level hooks
PostConfigInit,

// After PostConfigInit and before coreUobject initialized. used for early boot loading screens before the uobjects are initialized
PreEarlyLoadingScreen, // Since 4.20

// Loaded before the engine is fully initialized for modules that need to hook into the loading screen before it triggers
PreLoadingScreen,

// After the engine has been initialized
PostEngineInit,

// Do not automatically load this module
None,
}

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)和“可移植”的角度看,还是写个循环赋值靠谱一点。

UE4中接入SteamSDK

首先,检查引擎目录下:

1
Engine\Binaries\ThirdParty\Steamworks\Steamv139

该目录下是否有Win32/Win64文件夹,以及其中是否具有以下几个文件:

1
steam_api.dll  steamclient.dll  tier0_s.dll  vstdlib_s.dll

如果没有可以从Steam的安装目录拷贝过来。

之后打开项目,打开Plugins - Online Platfrom,确保启用以下三个插件:

  • Online Subsystem NULL
  • Online Subsystem Steam
  • Online Subsystem Utils

打开项目的*.target.cs文件,加入bUsesSteam = true;:

1
2
3
4
5
6
7
8
9
10
11
12
using UnrealBuildTool;
using System.Collections.Generic;

public class SanguoWarriorsTarget : TargetRules
{
public SanguoWarriorsTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
bUsesSteam = true;
ExtraModuleNames.AddRange( new string[] { "SanguoWarriors" } );
}
}

打开项目的*.build.cs,额外添加OnlineSubsystem的模块依赖:

1
2
3
4
5
6
7
8
9
10
11
PublicDependencyModuleNames.AddRange(
new string[] {
"OnlineSubsystem",
"OnlineSubsystemUtils",
});

PrivateDependencyModuleNames.AddRange(
new string[] {
"OnlineSubsystem",
"OnlineSubsystemUtils"
});

打开项目目录下的Config/DefaultEngine.ini,加入(或编辑)以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
[/Script/Engine.Engine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver")

[OnlineSubsystem]
DefaultPlatformService=Steam
PollingIntervalInMs=20

[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480
GameServerQueryPort=27015
bRelaunchInSteam=false

注意修改SteamDevAppId项为你自己的Steam内容的AppID.
上面的操作完毕之后,打开Steam(必须启动Steam才可以在游戏中使用Shift+Tab)唤出Steam的界面。

然后可以使用Standalone模式运行游戏,进入游戏后按下Shift+Tab如果可以唤出Steam,即为成功。

如果日志中出现这样的警告:

1
2
3
4
5
6
7
8
9
10
11
12
[2019.03.18-06.02.22:971][186]LogOnline: Warning: STEAM: Steamworks: SteamUtils() failed!
[2019.03.18-06.02.22:971][186]LogOnline: Warning: STEAM: Steamworks: SteamUser() failed!
[2019.03.18-06.02.22:972][186]LogOnline: Warning: STEAM: Steamworks: SteamFriends() failed!
[2019.03.18-06.02.22:972][186]LogOnline: Warning: STEAM: Steamworks: SteamRemoteStorage() failed!
[2019.03.18-06.02.22:973][186]LogOnline: Warning: STEAM: Steamworks: SteamUserStats() failed!
[2019.03.18-06.02.22:973][186]LogOnline: Warning: STEAM: Steamworks: SteamMatchmakingServers() failed!
[2019.03.18-06.02.22:973][186]LogOnline: Warning: STEAM: Steamworks: SteamApps() failed!
[2019.03.18-06.02.22:973][186]LogOnline: Warning: STEAM: Steamworks: SteamNetworking() failed!
[2019.03.18-06.02.22:974][186]LogOnline: Warning: STEAM: Steamworks: SteamMatchmaking() failed!
[2019.03.18-06.02.22:974][186]LogOnline: Display: STEAM: OnlineSubsystemSteam::Shutdown()
[2019.03.18-06.02.22:975][186]LogOnline: Warning: STEAM: Steam API failed to initialize!
[2019.03.18-06.02.22:975][186]LogOnline: Display: STEAM: OnlineSubsystemSteam::Shutdown()

这是因为启动游戏时,没有打开Steam,先启动Steam再启动游戏即可。

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

GENERATED_BODY和GENERATED_UCLASS_BODY的区别

GENERATED_BODY:

  • 不包含任何访问标识符,class默认是privatestruct默认是public.
  • 没有声明任何构造函数(constructor)

GENERATED_UCLASS_BODY:

  • 包含public访问标识符
  • 具有一个构造函数的声明,其参数为const FObjectInieializer&:
1
2
3
4
5
6
7
8
9
10
11
12
// .h file
// ...
GENERATED_BODY()

public:
AFPSGameMode(const class FObjectInitializer& ObjectInitializer);

// .cpp file
AFPSGameMode::AFPSGameMode(const class FObjectInitializer& ObjectInitializer)
{
// constructor functionality
}

参考链接:GENERATED_BODY vs GENERATED_UCLASS_BODY

Extensibility in UE4

UE4 Slate UI Framedowk

Networking in UE4

UE4 Engine Overview

Epic's Build Tools & Infrastructure

UHT的宏定义

GENERATED_BODY以及UPROPERTYUFUNCTION等宏的定义均是在:

1
Engine\Source\Runtime\CoreUObject\Public\UObject\ObjectMacros.h

信号量的竞态条件

[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 ]

UE:Get all registed Engine

可以使用IDesktopPlatform::EnumerateEngineInstallations来获取当前系统中所有从启动器安装、以及用户使用UnrealVersionSelector注册的引擎版本。

1
2
TMap<FString,FString> Installations;
FDesktopPlatformModule::Get()->EnumerateEngineInstallations(Installations)

UnrealVersionSelector注册引擎路径

使用UnrealVersionSelector注册引擎时,会将注册的引擎路径写入到注册表:

1
HKEY_CURRENT_USER\SOFTWARE\Epic Games\Unreal Engine\Builds

其注册表项的lpValueName则是生成的一个GUID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// DesktopPlatfromWindows.cpp
bool FDesktopPlatformWindows::RegisterEngineInstallation(const FString &RootDir, FString &OutIdentifier)
{
bool bRes = false;
if(IsValidRootDirectory(RootDir))
{
HKEY hRootKey;
if(RegCreateKeyEx(HKEY_CURRENT_USER, InstallationsSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hRootKey, NULL) == ERROR_SUCCESS)
{
FString NewIdentifier = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphensInBraces);
LRESULT SetResult = RegSetValueEx(hRootKey, *NewIdentifier, 0, REG_SZ, (const BYTE*)*RootDir, (RootDir.Len() + 1) * sizeof(TCHAR));
RegCloseKey(hRootKey);

if(SetResult == ERROR_SUCCESS)
{
OutIdentifier = NewIdentifier;
bRes = true;
}
}
}
return bRes;
}

UE并没有直接在IDesktopPlatfrom单独提供获取用户通过使用UnrealVersionSelector注册的引擎列表。
但是在EnumerateEngineInstallations中写了从注册表获取per-user installations引擎:

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
void FDesktopPlatformWindows::EnumerateEngineInstallations(TMap<FString, FString> &OutInstallations)
{
// Enumerate the binary installations
EnumerateLauncherEngineInstallations(OutInstallations);

// Enumerate the per-user installations
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER, InstallationsSubKey, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
{
// Get a list of all the directories
TArray<FString> UniqueDirectories;
OutInstallations.GenerateValueArray(UniqueDirectories);

// Enumerate all the installations
TArray<FString> InvalidKeys;
for (::DWORD Index = 0;; Index++)
{
TCHAR ValueName[256];
TCHAR ValueData[MAX_PATH];
::DWORD ValueType = 0;
::DWORD ValueNameLength = sizeof(ValueName) / sizeof(ValueName[0]);
::DWORD ValueDataSize = sizeof(ValueData);

LRESULT Result = RegEnumValue(hKey, Index, ValueName, &ValueNameLength, NULL, &ValueType, (BYTE*)&ValueData[0], &ValueDataSize);
if(Result == ERROR_SUCCESS)
{
int32 ValueDataLength = ValueDataSize / sizeof(TCHAR);
if(ValueDataLength > 0 && ValueData[ValueDataLength - 1] == 0) ValueDataLength--;

FString NormalizedInstalledDirectory(ValueDataLength, ValueData);
FPaths::NormalizeDirectoryName(NormalizedInstalledDirectory);
FPaths::CollapseRelativeDirectories(NormalizedInstalledDirectory);

if(IsValidRootDirectory(NormalizedInstalledDirectory) && !UniqueDirectories.Contains(NormalizedInstalledDirectory))
{
OutInstallations.Add(ValueName, NormalizedInstalledDirectory);
UniqueDirectories.Add(NormalizedInstalledDirectory);
}
else
{
InvalidKeys.Add(ValueName);
}
}
else if(Result == ERROR_NO_MORE_ITEMS)
{
break;
}
}

// Remove all the keys which weren't valid
for(const FString InvalidKey: InvalidKeys)
{
RegDeleteValue(hKey, *InvalidKey);
}

RegCloseKey(hKey);
}
}

EpicGameLauncher版引擎注册表路径

上面提到,如果非源码版引擎,使用UnrealVersionSelector注册时会写入到注册表:

1
HKEY_CURRENT_USER\SOFTWARE\Epic Games\Unreal Engine\Builds

从EpicGameLauncher安装的版本则会写入到另一个注册表路径下:

1
HKEY_LOCAL_MACHINE\SOFTWARE\EpicGames\Unreal Engine

其值为(从注册表导出的):

1
2
3
4
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\EpicGames\Unreal Engine\4.18]
"InstalledDirectory"="C:\\Program Files\\Epic Games\\UE_4.18"

UE提供了方法来得到从EpicGameLauncher安装的引擎(通过IDesktopPlatfrom接口):

1
2
TMap<FString,FString> Installations;
FDesktopPlatformModule::Get()->EnumerateLauncherEngineInstallations(OutInstallations);

UnrealVersionSelector的注册表文件关联

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
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.uproject]
@="Unreal.ProjectFile"

[HKEY_CLASSES_ROOT\Unreal.ProjectFile]
@="Unreal Engine Project File"

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\DefaultIcon]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell]

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\open]
@="Open"

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\open\command]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\" /editor \"%1\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\run]
@="Launch game"
"Icon"="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\run\command]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\" /game \"%1\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\rungenproj]
@="Generate Visual Studio project files"
"Icon"="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\rungenproj\command]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\" /projectfiles \"%1\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\switchversion]
@="Switch Unreal Engine version..."
"Icon"="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\switchversion\command]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\" /switchversion \"%1\""

注:如果想要为自己定义的文件加上启动方式,也可以使用这种方式。

UE Build:MakeShareable no matching

如果我们将一个类为T的C++原生指针通过MakeShareable的方式创建一个FRawPtrProxy<T>然后用来构造一个TSharedPtr<t>,有时会产生下列错误:

1
2
3
4
Engine\Source\Programs\UE4Launcher\Source\Private\UI\WidgetUELauncher.cpp(620): error C2672: 'MakeShareable': no matching overloaded function found
Engine\Source\Programs\UE4Launcher\Source\Private\UI\WidgetUELauncher.cpp(620): error C2780: 'SharedPointerInternals::FRawPtrProxy<ObjectType> MakeShareable(ObjectType *,DeleterType &&)': expects 2 arguments - 1 provided
Engine\Source\Runtime\Core\Public\Templates/SharedPointer.h(1666): note: see declaration of 'MakeShareable'
Engine\Source\Programs\UE4Launcher\Source\Private\UI\WidgetUELauncher.cpp(620): error C2784: 'SharedPointerInternals::FRawPtrProxy<ObjectType> MakeShareable(ObjectType *)': could not deduce template argument for 'ObjectType *' from 'TSharedRef<ObjectType,0>'

这是因为类型T的析构函数访问权限设置了保护,而这些引用计数的智能指针在计数为0时就会调用该对象的析构函数,所以会出现编译错误。
如果必须要创建一个TSharedPtr<T>,如果T为自己创建的SWidget派生类,可以把struct SharedPointerInternals::FRawPtrProxy<T>设置为友元(Makeshareable()就是构造了这个对象):

1
2
// SEditableBoxWraper is a custom Widget derived by SWidget 
friend struct SharedPointerInternals::FRawPtrProxy<SEditableBoxWraper>;

UE Package:Create a patch

最需要注意的是下面三点:
You can patch a project you have previously released using a versioned release. Some things to keep in mind are:

  • Lock down the serialization code paths at the time of release.
  • Keep the released cooked content, as the UnrealPak tool uses this to determine which content should be in the patch package file.
  • At runtime, mount both pak files, with a higher priority for the patch file so any content within it is loaded first.

总结来说就是,要保证与上次打包的资源路径不变/保留上次打包版本的Saved/Cooked目录(因为创建的Patch是通过UnrealPak来检测当前项目里的资源与上次Cooked的差异),程序运行时优先加载Patch的文件*0_P.pak,其中数字越大加载的优先级越高。

蓝图的功能实现也是uasset的,所以也会打包在pak里面,而不会在.exe里。蓝图是运行在虚拟机上的(详见Blueprint FAQ and Tips),并不是类似C++的直接编译成二进制文件,即蓝图也是资源,所以会打包在pak里面,这意味着如果纯用蓝图实现的项目可以不用变动.exe只增加patch就可以达到游戏更新。

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里生成的图书文件).

UE在编辑器内点Compile时自动保存资源

UE运行时动态更新寻路

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

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

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

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

UE EditorModule在Standalone下不会加载

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

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

UE版本号的宏定义

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

  • ENGINE_MAJOR_VERSION
  • ENGINE_MINOR_VERSION
  • ENGINE_PATCH_VERSION

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

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

引擎的版本信息也记录在Engine\Build\Build.version文件中:

1
2
3
4
5
6
7
8
9
10
{
"MajorVersion": 4,
"MinorVersion": 18,
"PatchVersion": 3,
"Changelist": 0,
"CompatibleChangelist": 3709383,
"IsLicenseeVersion": 0,
"IsPromotedBuild": 1,
"BranchName": ""
}

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

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

UE Actor Replication Config

Replication

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

Always Relevant(default false)

Always relevant for network(overrides bOnlyRelevantToOwner)

ReplicateMovement(default true)

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

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

NetLoadOnClient(default true)

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

NetUseOwnerReplovancy(default false)

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

Replicates(default true)

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

NetDormancy(default DORM_Awake)

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

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

NeuCullDistanceSquared(default 225000000.0)

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

NetUpdateFrequency(default 100.0)

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

MinNetUpdateFrequency(default 2.0)

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

NetPriority: (default 3.0)

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

Replicated Movement

LocationQuantization Level: (default EVectorQuantization::RoundTwoDecimals)

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

VelocityQuantization Level: (default ERotatorQuantization::RoundWholeNumber)

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

RotationQuantization Level(default ByteComponents)

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

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

UE Package Error: noexcept

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

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

*.target.cs中添加:

1
bForceEnableExceptions = true;

即可。

UE Package:Use Log in Shipping

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

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

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

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

则在*.target.cs中添加:

1
bForceEnableExceptions = true;

即可。

UE Log macro defined

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogHAL, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogMac, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogLinux, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogIOS, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogAndroid, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogPS4, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogXboxOne, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogWindows, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogSwitch, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogQuail, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogSerialization, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogUnrealMath, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogUnrealMatrix, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogContentComparisonCommandlet, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogNetPackageMap, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogNetSerialization, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogMemory, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogProfilingDebugging, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogCore, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogOutputDevice, Log, All);

CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogSHA, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogStats, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogStreaming, Display, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogInit, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogExit, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogExec, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogScript, Warning, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogLocalization, Error, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogLongPackageNames, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogProcess, Log, All);
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogLoad, Log, All);

// Temporary log category, generally you should not check things in that use this
CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogTemp, Log, All);

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

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

正则匹配C函数声明

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

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

UE蓝图的Paste工具

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

UE创建StandaloneApplication

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

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

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

用起来相当酸爽。

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

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

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

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

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

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

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

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

如题:

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

int main()
{
void(*fp)(int)=func;
fp(); // error: too few arguments to function call, expected 1, have 0
}

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

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

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

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

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

unspecified behavior

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

well-formed program

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

Implementation-Define(实现定义行为)

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

UB(未定义行为)

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

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

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

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

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

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

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

替换后:

然后重启即可。

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

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

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

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
class A { };
class B : private A { };
class C : public B {
A* p0;
};

int main(int argc,char* argv[])
{
C cobj;
return 0;
}

编译一下代码看一下:

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
class A { };
class B : private A { };
class C : public B {
::A* p0;
};

int main(int argc,char* argv[])
{
C cobj;
return 0;
}

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

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

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

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

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

指针到整型的转换

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

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

整型到指针的转换

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

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

uint64_t ui64val=(uint64_t)pval;
printf("cast uint64_t* to uint64_t resault is %x.\n",ui64val);

uint64_t *origVal=(uint64_t*)ui64val;
printf("cast uint64_t to uint64_t* resault is %x.\n",origVal);

return 0;
}
// output
ival address is 61fe38.
pval contain address is 61fe38.
cast uint64_t* to uint64_t resault is 61fe38.
cast uint64_t to uint64_t* resault is 61fe38.

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

函数指针的转换

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

1
2
3
4
5
6
7
8
9
10
11
12
13
void func(int ival)
{
printf("call func,the ival param is %d\n",ival);
}

int main()
{
void(*func_i)(int)=func;
void(*func_i_d)(int,double)=reinterpret_cast<void(*)(int,double)>(func_i);
func_i_d(123,45.6);

return 0;
}

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

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

修改Android的DNS

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

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

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

tracert命令

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

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

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

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

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

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

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

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

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

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

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

C++中的Standard Layout types

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

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

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

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

禁用MIUI的更新提示

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

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

修改为一个大的版本号:

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

保存后重启即可。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <cstdlib>

struct B {
virtual void f();
void mutate();
virtual ~B();
};
struct D1 : B { void f(); };
struct D2 : B { void f(); };
void B::mutate() {
new (this) D2; // reuses storage — ends the lifetime of *this
f(); // undefined behavior
... = this; // OK, this points to valid memory
}
void g() {
void* p = std::malloc(sizeof(D1) + sizeof(D2));
B* pb = new (p) D1;
pb->mutate();
&pb; // OK: pb points to valid memory
void* q = pb; // OK: pb points to valid memory
pb->f(); // undefined behavior, lifetime of *pb has ended
}

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

MinGW-W64编译32位程序

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

C语言的隐式函数声明

下面的代码:

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

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

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

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

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

1
extern int idenfifrer () ;

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

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

上面的代码相当于:

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

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

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

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

Guide to x86-64

Guide to x86-64

增强扫描版pdf

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

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

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

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

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

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

  • 转为pdf

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

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

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

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

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

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

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

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

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

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|Name=@FirewallAPI.dll,-28775|Desc=@FirewallAPI.dll,-28756|EmbedCtxt=@FirewallAPI.dll,-28752|

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

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

查看Linux系统上所有的服务

1
$ service --status-all

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

Linux防火墙放行端口

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

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

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

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

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

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

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

iptables的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
iptables v1.4.21

Usage: iptables -[ACD] chain rule-specification [options]
iptables -I chain [rulenum] rule-specification [options]
iptables -R chain rulenum rule-specification [options]
iptables -D chain rulenum [options]
iptables -[LS] [chain [rulenum]] [options]
iptables -[FZ] [chain] [options]
iptables -[NX] chain
iptables -E old-chain-name new-chain-name
iptables -P chain target [options]
iptables -h (print this help information)

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

一些参数缩写:

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

Linux下递归删除特定文件

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class T
{
public:
void print()
{
std::string(hw);
std::cout<<hw<<std::endl;
}
private:
std::string hw{"helloworld"};
};

int main(int argc,char* argv[])
{
T Obj;
Obj.print();

return 0;
}

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

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

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

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

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

在WSL中跑SS和KCP

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

1
2
3
4
5
# start kcp
$ nohup /etc/client_linux_amd64 --localaddr ${KCP_LOCAL_ADDR} --remoteaddr ${KCP_SERVER_ADDR:KCP_SERVER_PORT} --key ${KCP_PASSWORD} --crypt none --mode fast3 --mtu 1350 --sndwnd 512 --rcvwnd 512 --datashard 10 --parityshard 3 --dscp 0 --quiet false >/dev/null 2>&1 &

# start ss(using kcp)
$ python /usr/local/lib/python2.7/dist-packages/shadowsocks/local.py -s 127.0.0.1 -p ${KCP_LOCAL_ADDR} -k ${SS-SERVER-PASSWORD} -b 0.0.0.0 -l ${SS-LOCAL-PORT} -m aes-256-cfb -t 600

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

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

VC越界输出烫烫烫的原因

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

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

x32dbg调试:

通过IDA调试:

运行结果:

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

IDA的插件和配置

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

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

IDA安装findcrypt

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

# 之后安装 yara-python
$ pip install yara-python

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

Abd刷入Recovery

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

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

删除包含某些字符的行

在Linux下可以使用sed命令:

1
2
3
4
5
6
7
8
# 将abc.txt中所有含有"ABC"这个连续字符的行删除并写入到log.txt中
sed -e '/ABC/d/' abc.txt > log.txt
# 将自身所有含有"ABC"这个连续字符的行删除并保存
sed -i -e '/ABC/d' abc.txt

# 要删除的字符参数可以指定多个,只需要指定多个-e
# 将含有ABC和DEF的行都删除
sed -i -e '/ABC/d' -e '/DEF/d' abc.txt

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

启用Chrome自动禁用的插件

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

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

VS附加到进程的错误

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

MS2017-AttachToProcessField

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

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

Separate memory location

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

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

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

Clang的一个Bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A{
public:
void test(int){
printf("aaa\n");
}
};

extern "C" void _ZN1A4testEi(A*,int){
printf("bbb\n");
}

int main()
{
A a;
a.test(123);
return 0;
}

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

ISO C与POSIX对Byte定义的区别

ISO C

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

POSIX

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

LaTex插入符号

空格

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

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

分隔符

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

1
declaration\textrm{-}specifiers_{opt}

效果如下:

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

sizeof size of reference member of class

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

// The size?
sizeof(ATest);

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

1
%struct.ATest = type { i32* }

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

What is Translation Unit in C/C++?

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

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

Linux增加SWAP大小

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

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

Linux静态IP设置

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

1
2
3
4
5
6
7
8
9
10
11
# The loopback network interface
auto lo
iface lo inet loopback

# The primry network interface
auto eth0
# iface eth0 inet dhcp 为自动获取分配IP
iface eth0 inet static
address 192.168.2.111
netmask 255.255.255.0
geteway 192.168.2.1

一些cmd命令技巧

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

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

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

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

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

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

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

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

C语言跨scope的变量访问

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

1
2
3
4
5
6
7
int i=123;

void foo(void){
int i =456;
// 如何访问外部的i?
printf("%d",i);
}

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

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

Google C++ Style Guile

简易图片版:

中文PDF版:

C++17特性速查表

值类别速查表

浮点的零值

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

sendfile的优势

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

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

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

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

其作用为:

transfer data between file descriptors

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

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

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

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

new、构造函数和异常

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

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

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

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

指针比较的含义

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

C++中常见术语错误

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

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

为什么要有引用?

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

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

并发和并行(concurrency and parallel)

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

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

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

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

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

1
2
3
struct in_addr{
in_addr_t s_addr;
};

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

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

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

TCP/IP协议族

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

POSIX data types

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

MMU和COW

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
int x=12;
printf("start before: x = %d\n",x);
setbuf(stdout,NULL);
pid_t child;
if((child=fork())<0){
printf("error!\n");
}else{
if(child>0){
printf("x initial value on parent scope is %d address is %x\n",x,&x);
printf("parent! process ID is %d\n",getpid());
x=1;
printf("x on parent scope is %d address is %x\n\n",x,&x);
}else{
printf("x initial value on child scope is %d address is %x\n",x,&x);
printf("child! process ID is %d\n",getpid());
// x=2;
printf("x on child scope is %d address is %x\n\n",x,&x);
}
}
return 0;
}

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