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


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

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

History of Computer Animation

DirectX的资料文档

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

围绕Win10 DirectX 11平台

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

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

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

RoadMap

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

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

UE4:Clion debug project on MacOS

使用Clion打开项目之后设置debug(Run-Edit Configuration):

Executable选择为项目目录下的Binaries中编译出来的可执行文件,然后加上参数-game -log即可。

Chrome重启之后就暂停同步

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

UE:MODULE_NAME_API

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

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

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

MacOS则是一个空的宏定义:

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

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

Windows的为__declspec

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

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

UE:模块的宏定义

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

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

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

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

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

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

UE4:Listen Server模式下的SkipOwner

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

隐藏bat的黑窗口

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

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

MacOS安装UE

UE要求MacOS的分区格式为不区分大小写(不然EpicLauncher也无法安装),而且安装引擎的要求是系统版本大于10.13.5,否则会出现引擎崩溃和一些不支持的情况(尝试忽略错误无法安装成功)。
编译依赖xcode,就像依赖VS一样,需要安装编译环境。
如果安装完UE和Xcode之后创建项目提示下列错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
An error occurred while trying to generate project files.
Running Mono...
Setting up Mono
/Users/Shared/UnrealEngine/4.22/Engine /Users/Shared/UnrealEngine/4.22/Engine/Binaries/Mac
Discovering modules, targets and source code for project...
Compiling with non-standard Xcode (xcode-select): /Library/Developer/CommandLineTools/
Triggered an exception while looking for SDK directory in Xcode.app
System.IO.DirectoryNotFoundException: Directory '/Library/Developer/CommandLineTools/Platforms/MacOSX.platform/Developer/SDKs' not found.
at System.IO.Directory.ValidateDirectoryListing (System.String path, System.String searchPattern, System.Boolean& stop) [0x00000] in <filename unknown>:0
at System.IO.Directory.GetFileSystemEntries (System.String path, System.String searchPattern, FileAttributes mask, FileAttributes attrs) [0x00000] in <filename unknown>:0
at System.IO.Directory.GetDirectories (System.String path, System.String searchPattern) [0x00000] in <filename unknown>:0
at System.IO.Directory.GetDirectories (System.String path) [0x00000] in <filename unknown>:0
at UnrealBuildTool.AppleToolChain.SelectSDK (System.String BaseSDKDir, System.String OSPrefix, System.String& PlatformSDKVersion, Boolean bVerbose) [0x00000] in <filename unknown>:0
ERROR: Invalid SDK MacOSX.sdk, not found in /Library/Developer/CommandLineTools/Platforms/MacOSX.platform/Developer/SDKs

则安装Xcode Command Line Tools,然后执行以下命令即可:

1
$ sudo ln -s /Applications/Xcode.app/Contents/Developer/Platforms /Library/Developer/CommandLineTools/

使用VSC编写代码(UE_4.18+)

下面是UE的官方介绍:New: Visual Studio Code Supported on Windows, Mac and Linux

You can now use Visual Studio Code to write code on all UE4 host platforms. To use it, select "Visual Studio Code" as your preferred IDE from the editor preferences dialog, or add the -vscode argument when generating project files on the command line. All platforms are also required to have the .NET Core 2.0 runtimes installed, which can be obtained from the Microsoft .NET Core website.
To use Visual Studio Code to build and debug all project types, some additional extensions are required. On all platforms, make sure the Microsoft C/C++ extension, and the C# extension are installed. On Linux and Mac, the "Mono Debug" extension is required to debug C# projects, and the “LLDB Debugger” extension is required to debug C++ projects. Also, in order to debug C# projects, the mono runtime must be installed:

  • On OS X: brew install mono
  • On Linux:sudo apt-get install mono-complete

解除MacOS安装软件的限制

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

MacOS读写NTFS

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

首先,在终端下执行diskutil list查看磁盘信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
visionsmile$ diskutil list
/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *240.1 GB disk0
1: EFI 312.5 MB disk0s1
2: Microsoft Reserved 134.2 MB disk0s2
3: Microsoft Basic Data Windows 164.3 GB disk0s3
4: Apple_APFS Container disk1 75.3 GB disk0s4

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

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

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

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

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

1
sudo nano /etc/fstab

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

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

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

UE4:SpawnSoundAtLocation失败的情况

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

FAudioDevice::LocationIsAudible中做了检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
bool FAudioDevice::LocationIsAudible(const FVector& Location, const float MaxDistance) const
{
if (MaxDistance >= WORLD_MAX)
{
return true;
}

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

check(bInAudioThread || bInGameThread);

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Update the listener.
if (AudioDevice != NULL && PlayerController != NULL)
{
bool bUpdateListenerPosition = true;

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

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

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

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

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

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

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

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

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

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

MBR无损迁移到GPT

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

其构造函数的IR代码为:

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

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

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

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

其IR代码为:

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

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

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

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

Windows窗口切换快捷键

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

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

UE项目跨大版本升级

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

UE4:GC Config of BaseEngine.ini

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

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

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

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

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

Linux创建软链接

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

Linux添加PATH路径

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

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

或者:

1
$ echo $PATH

输出当前用户的PATH路径。

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

重启之后生效。

变量模板的特化

求和:

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

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

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

斐波那契第N项:

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

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

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

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

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

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

Windows查看程序的网络连接

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

1
netstat -ano|findstr PID

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

What is the Object in C/C++?

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

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

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

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

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

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

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

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

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

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

VS修改文件编码

选择Tools-Customize-Commands:

File下添加Advanced Save Options选项:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

执行完毕即可解决错误。

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

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

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

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

他们的区别是:

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

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

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

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

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

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

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

萃取数组的元素个数

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

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

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

Chrome浏览器由所属组织管理

解决办法,删除:

1
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome

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

Bin2Hex

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

下载bin2hex,用法如下:

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

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

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

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

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

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

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

C读写二进制文件

读:

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

写:

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

龙书DX12勘误

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

左手坐标系和右手坐标系


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

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

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

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

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

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

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

// output
2 2 2 2 2

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
###############################
##define ARRAY_NUM 5
#int main(void)
#{
# struct { int ival;double dval; } DataList[ARRAY_NUM];
#
# for(int index=0;index<ARRAY_NUM;++index)
# DataList[index].ival=2;
#}
###############################
.text
.def main;
.scl 2;
.type 32;
.endef
.globl main # -- Begin function main
.p2align 4, 0x90
main: # @main
.seh_proc main
# %bb.0:
pushq %rbp
.seh_pushreg 5
subq $144, %rsp
.seh_stackalloc 144
leaq 128(%rsp), %rbp
.seh_setframe 5, 128
.seh_endprologue
callq __main
movl $0, 12(%rbp)
movl $0, -84(%rbp)
.LBB0_1: # =>This Inner Loop Header: Depth=1
cmpl $5, -84(%rbp)
jge .LBB0_4
# %bb.2: # in Loop: Header=BB0_1 Depth=1
movslq -84(%rbp), %rax
shlq $4, %rax
leaq -80(%rbp), %rcx
addq %rax, %rcx
movl $2, (%rcx)
# %bb.3: # in Loop: Header=BB0_1 Depth=1
movl -84(%rbp), %eax
addl $1, %eax
movl %eax, -84(%rbp)
jmp .LBB0_1
.LBB0_4:
xorl %eax, %eax
addq $144, %rsp
popq %rbp
retq
.seh_handlerdata
.text
.seh_endproc
# -- End function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
###############################
##define ARRAY_NUM 5
#int main(int argc,char* argv[])
#{
# struct { int ival;double dval; } DataList[ARRAY_NUM] ={ [0 ... ARRAY_NUM-1].ival = 2 };
#}
###############################
.text
.def main;
.scl 2;
.type 32;
.endef
.globl main # -- Begin function main
.p2align 4, 0x90
main: # @main
.seh_proc main
# %bb.0:
pushq %rbp
.seh_pushreg 5
subq $128, %rsp
.seh_stackalloc 128
leaq 128(%rsp), %rbp
.seh_setframe 5, 128
.seh_endprologue
callq __main
xorl %eax, %eax
movl $80, %ecx
movl %ecx, %r8d
leaq -80(%rbp), %rdx
movq %rdx, %rcx
movl %eax, %edx
movl %eax, -84(%rbp) # 4-byte Spill
callq memset
movl $2, -80(%rbp)
movl $2, -64(%rbp)
movl $2, -48(%rbp)
movl $2, -32(%rbp)
movl $2, -16(%rbp)
movl -84(%rbp), %eax # 4-byte Reload
addq $128, %rsp
popq %rbp
retq
.seh_handlerdata
.text
.seh_endproc
# -- End function

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

信号量的竞态条件

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

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

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

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

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

CMD命令:拷贝文件夹

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

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

CMD命令:创建多级目录

1
md 123\456\789

tar.gz的压缩与解压缩

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

!!的用法

看到!的这样一个用法:

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

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

C语言中的枚举就是整型

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

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

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

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

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

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

使用Calibre转换繁体为简体

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

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

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

从GooglePlay下载图书并去除DRM

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

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

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

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


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

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

为ADE添加一个key:

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

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

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

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

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

正则匹配C函数声明

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

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

UE蓝图的Paste工具

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

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

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

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

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

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

如题:

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

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

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

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

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

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

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

unspecified behavior

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

well-formed program

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

Implementation-Define(实现定义行为)

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

UB(未定义行为)

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

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

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

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

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

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

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

替换后:

然后重启即可。

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

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

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

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

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

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

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

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

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

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

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

编译一下代码看一下:

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

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

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

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

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

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

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

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

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

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

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

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

指针到整型的转换

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

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

整型到指针的转换

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

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

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

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

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

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

函数指针的转换

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

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

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

return 0;
}

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

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

修改Android的DNS

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

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

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

tracert命令

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

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

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

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

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

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

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

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

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

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

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

C++中的Standard Layout types

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

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

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

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

禁用MIUI的更新提示

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

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

修改为一个大的版本号:

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

保存后重启即可。

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

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

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

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

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

MinGW-W64编译32位程序

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

C语言的隐式函数声明

下面的代码:

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

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

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

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

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

1
extern int idenfifrer () ;

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

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

上面的代码相当于:

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

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

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

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

Guide to x86-64

Guide to x86-64

增强扫描版pdf

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

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

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

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

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

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

二值化图片
扫描的pdf很多都是泛黄的,影响观感,可以通过ImageMagick将图片处理为黑白的:

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

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

  • 转为pdf

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

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

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

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

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

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

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

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

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

Clang查看对象的内存布局

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

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

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

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

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// class_layout.cpp

class A{
public:
virtual void func(int ival=0){}
};

class B:public A{
public:
virtual void func(int ival=123){}
};

class C:public B{
public:
virtual void func(int ival=123){}
};

int main()
{
C cobj;
cobj.func();
B &bobj=cobj;
bobj.func();
A &aobj=cobj;
aobj.func();
}

预处理:

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

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

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

*** Dumping AST Record Layout
0 | class B
0 | class A (primary base)
0 | (A vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]

*** Dumping AST Record Layout
0 | class C
0 | class B (primary base)
0 | class A (primary base)
0 | (A vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]

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

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

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

考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class A{
public:
virtual void func(int ival=0)
{
std::cout<<"A::func,arg is "<<ival<<endl;
}
};

class B:public A{
public:
virtual void func(int ival=123)
{
std::cout<<"B::func,arg is "<<ival<<endl;
}
};

class C:public B{
public:
virtual void func(int ival=456)
{
std::cout<<"C::func,arg is "<<ival<<endl;
}
};

int main()
{
C cobj;
cobj.func(); // print what?
B &bobj=cobj;
bobj.func(); // print what?
A &aobj=cobj;
aobj.func(); //print what?
}


// output
C::func,arg is 456
C::func,arg is 123
C::func,arg is 0

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
define i32 @main() #4 {
%1 = alloca %class.C, align 8
%2 = alloca %class.B*, align 8
%3 = alloca %class.A*, align 8
// class C的构造函数
call void @_ZN1CC2Ev(%class.C* %1) #3
// 直接通过C类型的对象cobj调用func
call void @_ZN1C4funcEi(%class.C* %1, i32 456)

// 通过B类型的引用bobj用来调用func
%4 = bitcast %class.C* %1 to %class.B*
store %class.B* %4, %class.B** %2, align 8
%5 = load %class.B*, %class.B** %2, align 8
%6 = bitcast %class.B* %5 to void (%class.B*, i32)***
%7 = load void (%class.B*, i32)**, void (%class.B*, i32)*** %6, align 8
%8 = getelementptr inbounds void (%class.B*, i32)*, void (%class.B*, i32)** %7, i64 0
%9 = load void (%class.B*, i32)*, void (%class.B*, i32)** %8, align 8
// 该调用的默认参数在编译时已经确定为B::func的版本,不会推迟到动态绑定时
call void %9(%class.B* %5, i32 123)

// 通过A类型的引用aobj来调用func
%10 = bitcast %class.C* %1 to %class.A*
store %class.A* %10, %class.A** %3, align 8
%11 = load %class.A*, %class.A** %3, align 8
%12 = bitcast %class.A* %11 to void (%class.A*, i32)***
%13 = load void (%class.A*, i32)**, void (%class.A*, i32)*** %12, align 8
%14 = getelementptr inbounds void (%class.A*, i32)*, void (%class.A*, i32)** %13, i64 0
%15 = load void (%class.A*, i32)*, void (%class.A*, i32)** %14, align 8
// 该调用的默认参数在编译时已经确定为A::func的版本,不会推迟到动态绑定时
call void %15(%class.A* %11, i32 0)

// ...
}

Windwos上使用MinGW编译LLVM

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

1
2
3
4
5
6
7
8
9
10
11
12
13
$ mkdir build
$ cd build

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

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

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

解决cmder中文乱码


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

apk的反编译与签名

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 反编译apk
$ apktool.bat d -o <output_dir> <input.apk>
# 修改完成后重新编译apk
$ apktool.bat b -o <output.apk> <input_dir>

# 生成keystore文件

# -genkey 产生证书文件
# -alias 产生别名
# -keystore 指定密钥库的.keystore文件中
# -keyalg 指定密钥的算法,这里指定为RSA(非对称密钥算法)
# -validity 为证书有效天数,这里我们写的是40000天
$ keytool -genkey -alias demo.keystore -keyalg RSA -validity 40000 -keystore demo.keystore

# 签名到appk,记得替换<input.apk>为需要签名的apk文件
$ jarsigner -verbose -keystore demo.keystore <input.apk> demo.keystore

C++ Lambda的捕获

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

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

看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public:
int a=456;class A{

A(){}
A(const A& aobj):a{aobj.a}{}
};

int main()
{
int a=123;
A aobj;

auto lambdaObj=[=](){cout<<a<<'\t'<<aobj.a<<endl;};
cout<<sizeof(lambdaObj)<<endl;
lambdaObj();
}
// output
8
123 456

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

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

// lambdaObj class type
%class.anon = type { i32, %class.A }

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
define i32 @main() #4 {
%1 = alloca i32, align 4
%2 = alloca %class.A, align 4
store i32 123, i32* %1, align 4
call void @_ZN1AC2Ev(%class.A* %2)

// 生成出来的闭包对象
%3 = alloca %class.anon, align 4

// 将捕获的参数赋值为类的参数
%4 = getelementptr inbounds %class.anon, %class.anon* %3, i32 0, i32 0
%5 = load i32, i32* %1, align 4
store i32 %5, i32* %4, align 4
%6 = getelementptr inbounds %class.anon, %class.anon* %3, i32 0, i32 1
// 调用闭包对象内类成员A的拷贝构造A::A(const A&)
call void @_ZN1AC2ERKS_(%class.A* %6, %class.A* dereferenceable(4) %2)

// ...
}

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

考虑如下情况:

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

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

树莓派命令行配置连接Wifi

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

1
2
3
4
5
6
7
8
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
ssid="APName"
psk="password"
key_mgmt=WPA-PSK
}

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

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

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

1
powercfg -h off

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

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

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

Windows窗口快捷键

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

Wget下载网站

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

参数如下:

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

Linux查看nohup.out

1
$ tail -fn 50 nohup.out

Lambda-Expressions Syntax

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

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

先看第一行:

1
[](){};

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

第二行:

1
[]{}();

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

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

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

第三行:

1
{}[]{};

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

1
2
3
4
{

}
[]{};

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

WSL换源

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

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

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

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

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

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

然后更新即可:

1
$ sudo apt-get update

清理Mstsc的历史记录

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

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

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

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

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

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

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

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

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

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

1
v2.28|Action=Allow|Active=FALSE|Dir=In|Protocol=6|LPort=3389|App=%SystemRoot%\system32\svchost.exe|Svc=termservice|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