内存不能为written修复工具(该内存不能read?已解决)
内存不能为written修复工具文章列表:
- 1、该内存不能read?已解决
- 2、电脑常用小知识收藏
- 3、Linux下用rm误删除文件的三种恢复方法
- 4、在 Linux 上创建并调试转储文件 | Linux 中国
- 5、C++ 标准from 11 to 20主要新增特性
该内存不能read?已解决
在我们使用电脑是时候,遇到这种情况“不能为read或者written。”
先说几个常见的原因:
1.驱动程序不稳定,或者与操作系统、计算机硬件不兼容。
2.不小心安装了流氓软件或IE流氓插件;
3.正在加载的程序与已经运行的发生冲突,如有的杀毒软件实时监控程序,会和其它杀毒软件争夺系统控制权,导致内存不能为read;
4.计算机感染了病毒;
5.玩游戏时出现内存不能为Read,则是因为显卡驱动不匹配;
6.夏天时硬件过热,散热不好,导致内存故障,不能为Read;
7.内存条与主板兼容性问题,导致内存不能为Read。
我的解决方法:
因为我是之前装python跟sublime的时候还不懂设置环境变量导致的错误。先试用了方法一仍未能解决,后面在环境变量,系统-path里面增加了以下代码:%SystemRoot%system32;%SystemRoot%;%SystemRoot%System32Wbem。
常见方法一:
WIN R,打开电脑终端窗口输入cmd,复制粘贴(粘贴只可鼠标右键,不能CTRL V)代码for %1 in (%windir%system32*.ocx) do regsvr32 /s %1;出现以下结果:
然后再复制粘贴,for %1 in (%windir%system32*.dll) do regsvr32.exe /s %1
等待命令执行(滚动)完毕,出现以下结果:
然后关掉,重启。
顺便讲一下会出现的一个情况:“不是内部命令或外部命令,也不是可运行程序”。这个可以看上一期。
方法二:拆开电脑,拔下内存条,重新插上。
看过我往期的朋友就知道我的电脑从来不用360,电脑管家一类的。所以就没用上这些工具,电脑不轻易出现问题,网速也是相当快。
更多资讯,请关注肖大锐~
Linux下用rm误删除文件的三种恢复方法
对于rm,很多人都有惨痛的教训。我也遇到一次,一下午写的程序就被rm掉了,幸好只是一个文件,第二天很快又重新写了一遍。但是很多人可能就不像我这么幸运了。本文收集了一些在Linux下恢复rm删除的文件的方法,给大家作为参考。
1.几点建议避免误删
首先,最好的方法是避免这个问题,以下是几点建议:
1、rm -rf误操作的后果是可怕的,rm -f也要三思而行,不能轻易使用。
2、做好数据备份。
3、用一些策略避免出错:
提倡在shell下用 TAB 补全,用脚本执行任务,减少出错的机会。或者编写一个脚本,起名rm,在脚本里将真实的rm改为mv ,将删除的都mv到一个指定的目录里面,定期清理。
那么rm删除的文件还能恢复吗?
rm的man里面有如下说法:
请注意,如果使用 rm 来删除文件,通常仍可以将该文件恢复原状。如果想保证该文件的内容无法还原,请考虑使用 shred。
所以理论上rm删除的文件是还能恢复的。删掉文件其实只是将指向数据块的索引点(information nodes)释放,只要不被覆盖,数据其实还在硬盘上,关键在于找出索引点,然后将其所指数据块内的数据抓出,再保存到另外的分区。在用rm误删除文件后,我们要做的第一件事就是保证不再向误删文件的分区写数据。
2.使用lsof命令恢复
lsof命令用于查看你进程开打的文件,打开文件的进程,进程打开的端口(TCP、UDP)。找回/恢复删除的文件。是十分方便的系统监视工具,因为lsof命令需要访问核心内存和各种文件,所以需要root用户执行。
在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。
1.语法
lsof(选项)
2.参数
-a:列出打开文件存在的进程; -c<进程名>:列出指定进程所打开的文件; -g:列出GID号进程详情; -d<文件号>:列出占用该文件号的进程; d<目录>:列出目录下被打开的文件; D<目录>:递归列出目录下被打开的文件; -n<目录>:列出使用NFS的文件; -i<条件>:列出符合条件的进程。(4、6、协议、:端口、 @ip ) -p<进程号>:列出指定进程号所打开的文件; -u:列出UID号进程详情; -h:显示帮助信息; -v:显示版本信息。
3.使用
查看
lsof -i:(端口) 查看这个端口有那些进程在访问,比如22端口
shell> lsof -i:22COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEsshd 1939 root 3u IPv4 12317 0t0 TCP *:ssh (LISTEN)sshd 1939 root 4u IPv6 12321 0t0 TCP *:ssh (LISTEN)sshd 2790 root 3u IPv4 15229 0t0 TCP 192.168.178.128:ssh->192.168.178.1:64601 (ESTABLISHED)sshd 2824 root 3u IPv4 15528 0t0 TCP 192.168.178.128:ssh->192.168.178.1:64673 (ESTABLISHED)sshd 2990 root 3u IPv4 15984 0t0 TCP 192.168.178.128:ssh->192.168.178.1:64686 (ESTABLISHED)sshd 14695 root 3u IPv4 39558 0t0 TCP 192.168.178.128:ssh->192.168.178.1:49662 (ESTABLISHED)
lsof输出各列信息的意义如下:
COMMAND:进程的名称
PID:进程标识符
USER:进程所有者
FD:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等
TYPE:文件类型,如DIR、REG等
DEVICE:指定磁盘的名称
SIZE:文件的大小
NODE:索引节点(文件在磁盘上的标识)
NAME:打开文件的确切名称
恢复文件
利用lsof可以恢复一些系统日志,前提是这个进程必须存在。这里就拿最常用的/var/log/messages来举例说明,大家在做测试的时候最好先备份一下。
#备份shell> cp /var/log/message /var/log/message_bacshell> lsof |grep /var/log/messagersyslogd 1737 root 1w REG 8,2 5716123 652638 /var/log/messages
进程在运行中,接下来我就把/var/log/messages这个文件删掉
shell> rm /var/log/messages
删掉之后,我再来看看这个进程的变化
shell> lsof |grep /var/log/messagesrsyslogd 1737 root 1w REG 8,2 5716123 652638 /var/log/messages (deleted)
大家看到有变化了吧, 对比两个之后发现多了(deleted)。要找到这个文件在哪还要看看这个
PID:1737 FD:1 那我们有直接进入/proc/1737/FD/1用ll查看一下
shell> cd /proc/1737/fd/shell> lltotal 0lrwx------ 1 root root 64 Dec 23 13:00 0 -> socket:[11442]l-wx------ 1 root root 64 Dec 23 13:00 1 -> /var/log/messages (deleted)l-wx------ 1 root root 64 Dec 23 13:00 2 -> /var/log/securelr-x------ 1 root root 64 Dec 23 13:00 3 -> /proc/kmsgl-wx------ 1 root root 64 Dec 23 13:00 4 -> /var/log/maillog
看到了1对应/var/log/messages (deleted),看看文件是不是我们要的文件:
shell> head -5 1Nov 14 03:11:11 localhost kernel: imklog 5.8.10, log source = /proc/kmsg started.Nov 14 03:11:11 localhost rsyslogd: [origin software="rsyslogd" swVersion="5.8.10" x-pid="1241" x-info="http://www.rsyslog.com"] startNov 14 03:11:11 localhost kernel: Initializing cgroup subsys cpusetNov 14 03:11:11 localhost kernel: Initializing cgroup subsys cpuNov 14 03:11:11 localhost kernel: Linux version 2.6.32-431.el6.x86_64 (mockbuild@c6b8.bsys.dev.centos.org) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC) ) #1 SMP Fri Nov 22 03:15:09 UTC 2013
对比备份文件:
shell> head -5 /var/log/message_bacNov 14 03:11:11 localhost kernel: imklog 5.8.10, log source = /proc/kmsg started.Nov 14 03:11:11 localhost rsyslogd: [origin software="rsyslogd" swVersion="5.8.10" x-pid="1241" x-info="http://www.rsyslog.com"] startNov 14 03:11:11 localhost kernel: Initializing cgroup subsys cpusetNov 14 03:11:11 localhost kernel: Initializing cgroup subsys cpuNov 14 03:11:11 localhost kernel: Linux version 2.6.32-431.el6.x86_64 (mockbuild@c6b8.bsys.dev.centos.org) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC) ) #1 SMP Fri Nov 22 03:15:09 UTC 2013
对比发现数据是一样的,恢复
shell> cat 1 > /var/log/messages
再次提醒,恢复前提是这个进程必须存在。
3.使用extundelete工具
extundelete工具安装
extundelete下载地址:http://extundelete.sourceforge.net/
wget https://nchc.dl.sourceforge.net/project/extundelete/extundelete/0.2.4/extundelete-0.2.4.tar.bz2
解压该文件tar jxvf extundelete-0.2.4.tar.bz2
若报这种错误
[root@docking ~]# tar jxvf extundelete-0.2.4.tar.bz2 tar (child): bzip2:无法 exec: 没有那个文件或目录tar (child): Error is not recoverable: exiting nowtar: Child returned status 2tar: Error is not recoverable: exiting now
则使用yum -y install bzip2进行解决
[root@docking ~]# tar jxvf extundelete-0.2.4.tar.bz2 extundelete-0.2.4/extundelete-0.2.4/acinclude.m4extundelete-0.2.4/missingextundelete-0.2.4/autogen.shextundelete-0.2.4/aclocal.m4extundelete-0.2.4/configureextundelete-0.2.4/LICENSEextundelete-0.2.4/README...................................................
cd extundelete-0.2.4./configure
若这步骤报错
[root@docking extundelete-0.2.4]# ./configure Configuring extundelete 0.2.4configure: error: in `/root/extundelete-0.2.4':configure: error: C compiler cannot create executablesSee `config.log' for more details
则使用yum -y install gcc-c 解决.
若执行上一步仍然报错,
[root@docking extundelete-0.2.4]# ./configure Configuring extundelete 0.2.4configure: error: Can't find ext2fs library
则使用yum -y install e2fsprogs e2fsprogs-devel来解决。
#Ubuntu的解决办法为sudo apt-get install e2fslibs-dev e2fslibs-dev
不出意外的话到这里应该configure能够顺利完成.
[root@docking extundelete-0.2.4]# ./configure Configuring extundelete 0.2.4Writing generated files to disk[root@docking extundelete-0.2.4]#
最后make然后 make install
[root@docking extundelete-0.2.4]# makemake -s all-recursiveMaking all in srcextundelete.cc: 在函数‘ext2_ino_t find_inode(ext2_filsys, ext2_filsys, ext2_inode*, std::string, int)’中:extundelete.cc:1272:29: 警告:在 {} 内将‘search_flags’从‘int’转换为较窄的类型‘ext2_ino_t {aka unsigned int}’ [-Wnarrowing] buf, match_name2, priv, 0}; ^[root@docking extundelete-0.2.4]# make installMaking install in src /usr/bin/install -c extundelete '/usr/local/bin'
extundelete安装完成.
扫描误删除的文件:
使用df -lh查看挂载:
taroballs@taroballs-PC:~$ df -lh文件系统 容量 已用 可用 已用% 挂载点udev 1.9G 0 1.9G 0% /devtmpfs 387M 1.8M 385M 1% /run/dev/sda2 92G 61G 26G 71% /tmpfs 1.9G 49M 1.9G 3% /dev/shmtmpfs 5.0M 4.0K 5.0M 1% /run/locktmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup/dev/sda3 104G 56G 44G 57% /hometmpfs 387M 40K 387M 1% /run/user/1000/dev/sda4 70G 20G 47G 30% /media/taroballs/d8423f8c-d687-4c03-a7c8-06a7fb57f96d/dev/sdb1 6.8G 4.1G 2.8G 60% /media/taroballs/taroballs/dev/sr0 4.0G 4.0G 0 100% /media/taroballs/2018-01-16-12-36-00-00taroballs@taroballs-PC:~$ cd /media/taroballs/taroballs/taroballs@taroballs-PC:/media/taroballs/taroballs$
可以看到,我们的目录/media/taroballs/taroballs
挂载到/dev/sdb1 这个文件系统中.
umount我们的挂载盘
比如:
taroballs@taroballs-PC:~$ df -lh | grep /dev/sdb1/dev/sdb1 6.8G 4.1G 2.8G 60% /media/taroballs/taroballs
umount这个目录
taroballs@taroballs-PC:~$ umount /media/taroballs/taroballstaroballs@taroballs-PC:~$ df -lh | grep /dev/sdb1taroballs@taroballs-PC:~$ #记得删除一定要后umount哦,不然二次写入谁也帮不了你呢。
通过inode节点恢复
taroballs@taroballs-PC:~$ mkdir recovertesttaroballs@taroballs-PC:~$ cd recovertest/taroballs@taroballs-PC:~/recovertest$
执行恢复extundelete /dev/sdb1 --inode 2
taroballs@taroballs-PC:/media/taroballs/taroballs$ sudo extundelete /dev/sdb1 --inode 2NOTICE: Extended attributes are not restored.Loading filesystem metadata ... 8 groups loaded.Group: 0Contents of inode 2: ..省略N行 File name | Inode number | Deleted status. 2.. 2deletetest 12 Deletedtmppasswd 14 Deleted
通过扫描发现了我们删除的文件夹,现在执行恢复操作。
(1)恢复单一文件tmppasswd
taroballs@taroballs-PC:~/recovertest$ extundelete /dev/sdb1 --restore-file passwd NOTICE: Extended attributes are not restored.Loading filesystem metadata ... 8 groups loaded.Loading journal descriptors ... 46 descriptors loaded.Successfully restored file tmppasswd
恢复文件是放到了当前目录RECOVERED_FILES。
查看恢复的文件:
taroballs@taroballs-PC:~/recovertest$ cat tmppasswd tcpdump:x:172:72::/:/sbin/nologin
(2)恢复目录deletetest
extundelete /dev/sdb1 --restore-directory deletetestNOTICE: Extended attributes are not restored.Loading filesystem metadata ... 8 groups loaded.Loading journal descriptors ... 46 descriptors loaded.Searching for recoverable inodes in directory deletetest ... 5 recoverable inodes found.Looking through the directory structure for deleted files ...
(3)恢复所有
taroballs@taroballs-PC:~/recovertest$ extundelete /dev/sdb1 --restore-allNOTICE: Extended attributes are not restored.Loading filesystem metadata ... 8 groups loaded.Loading journal descriptors ... 46 descriptors loaded.Searching for recoverable inodes in directory / ... 5 recoverable inodes found.Looking through the directory structure for deleted files ... 0 recoverable inodes still lost. taroballs@taroballs-PC:~/recovertest$ tree backuptest/├── deletetest│ └── innerfolder│ └── deletefile.txt└── tmppasswd2 directories, 2 files
(4)恢复指定inode
taroballs@taroballs-PC:~/recovertest$ extundelete /dev/sdb1 --restore-inode 14NOTICE: Extended attributes are not restored.Loading filesystem metadata ... 8 groups loaded.Loading journal descriptors ... 46 descriptors loaded.taroballs@taroballs-PC:~/recovertest$ cat file.14 tcpdump:x:172:72::/:/sbin/nologin#注意恢复inode的时候,恢复 出来的文件名和之前不一样,需要单独进行改名。
最后附上extundelete的用法:
$ extundelete --helpUsage: extundelete [options] [--] device-fileOptions: --version, -[vV] Print version and exit successfully. --help, Print this help and exit successfully. --superblock Print contents of superblock in addition to the rest. If no action is specified then this option is implied. --journal Show content of journal. --after dtime Only process entries deleted on or after 'dtime'. --before dtime Only process entries deleted before 'dtime'.Actions: --inode ino Show info on inode 'ino'. --block blk Show info on block 'blk'. --restore-inode ino[,ino,...] Restore the file(s) with known inode number 'ino'. The restored files are created in ./RECOVERED_FILES with their inode number as extension (ie, file.12345). --restore-file 'path' Will restore file 'path'. 'path' is relative to root of the partition and does not start with a '/' The restored file is created in the current directory as 'RECOVERED_FILES/path'. --restore-files 'path' Will restore files which are listed in the file 'path'. Each filename should be in the same format as an option to --restore-file, and there should be one per line. --restore-directory 'path' Will restore directory 'path'. 'path' is relative to the root directory of the file system. The restored directory is created in the output directory as 'path'. --restore-all Attempts to restore everything. -j journal Reads an external journal from the named file. -b blocknumber Uses the backup superblock at blocknumber when opening the file system. -B blocksize Uses blocksize as the block size when opening the file system. The number should be the number of bytes. --log 0 Make the program silent. --log filename Logs all messages to filename.--log D1=0,D2=filename Custom control of log messages with comma-separated Examples below: list of options. Dn must be one of info, warn, or --log info,error error. Omission of the '=name' results in messages --log warn=0 with the specified level to be logged to the console. --log error=filename If the parameter is '=0', logging for the specified level will be turned off. If the parameter is '=filename', messages with that level will be written to filename. -o directory Save the recovered files to the named directory. The restored files are created in a directory named 'RECOVERED_FILES/' by default.
在 Linux 上创建并调试转储文件 | Linux 中国
了解如何处理转储文件将帮你找到应用中难以重现的 bug。
• 来源:linux.cn • 作者:Stephan Avenwedde • 译者:Xingyu.Wang •
(本文字数:5501,阅读时长大约:6 分钟)
崩溃转储、内存转储、核心转储、系统转储……这些全都会产生同样的产物:一个包含了当应用崩溃时,在那个特定时刻应用的内存状态的文件。
这是一篇指导文章,你可以通过克隆示例的应用仓库来跟随学习:
git clone https://github.com/hANSIc99/core_dump_example.git
信号如何关联到转储
信号是操作系统和用户应用之间的进程间通讯。Linux 使用 POSIX 标准 中定义的信号。在你的系统上,你可以在 /usr/include/bits/signum-generic.h 找到标准信号的定义。如果你想知道更多关于在你的应用程序中使用信号的信息,这有一个信息丰富的 signal 手册页 。简单地说,Linux 基于预期的或意外的信号来触发进一步的活动。
当你退出一个正在运行的应用程序时,应用程序通常会收到 SIGTERM 信号。因为这种类型的退出信号是预期的,所以这个操作不会创建一个内存转储。
以下信号将导致创建一个转储文件(来源: GNU C库 ):
SIGFPE:错误的算术操作
SIGILL:非法指令
SIGSEGV:对存储的无效访问
SIGBUS:总线错误
SIGABRT:程序检测到的错误,并通过调用 abort() 来报告
SIGIOT:这个信号在 Fedora 上已经过时,过去在 PDP-11 上用 abort() 时触发,现在映射到 SIGABRT
创建转储文件
导航到 core_dump_example 目录,运行 make,并使用 -c1 开关执行该示例二进制:
./coredump -c1
该应用将以状态 4 退出,带有如下错误:
Dump written
“Abgebrochen (Speicherabzug geschrieben) ”(LCTT 译注:这是德语,应该是因为本文作者系统是德语环境)大致翻译为“分段故障(核心转储)”。
是否创建核心转储是由运行该进程的用户的资源限制决定的。你可以用 ulimit 命令修改资源限制。
检查当前创建核心转储的设置:
ulimit -c
如果它输出 unlimited,那么它使用的是(建议的)默认值。否则,用以下方法纠正限制:
ulimit -c unlimited
要禁用创建核心转储,可以设置其大小为 0:
ulimit -c 0
这个数字指定了核心转储文件的大小,单位是块。
什么是核心转储?
内核处理核心转储的方式定义在:
/proc/sys/kernel/core_pattern
我运行的是 Fedora 31,在我的系统上,该文件包含的内容是:
/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h
这表明核心转储被转发到 systemd-coredump 工具。在不同的 Linux 发行版中,core_pattern 的内容会有很大的不同。当使用 systemd-coredump 时,转储文件被压缩保存在 /var/lib/systemd/coredump 下。你不需要直接接触这些文件,你可以使用 coredumpctl。比如说:
coredumpctl list
会显示系统中保存的所有可用的转储文件。
使用 coredumpctl dump,你可以从最后保存的转储文件中检索信息:
[stephan@localhost core_dump_example]$ ./coredump Application started…(…….)Message: Process 4598 (coredump) of user 1000 dumped core.Stack trace of thread 4598:#0 0x00007f4bbaf22625 __GI_raise (libc.so.6)#1 0x00007f4bbaf0b8d9 __GI_abort (libc.so.6)#2 0x00007f4bbaf664af __libc_message (libc.so.6)#3 0x00007f4bbaf6da9c malloc_printerr (libc.so.6)#4 0x00007f4bbaf6f49c _int_free (libc.so.6)#5 0x000000000040120e n/a (/home/stephan/Dokumente/core_dump_example/coredump)#6 0x00000000004013b1 n/a (/home/stephan/Dokumente/core_dump_example/coredump)#7 0x00007f4bbaf0d1a3 __libc_start_main (libc.so.6)#8 0x000000000040113e n/a (/home/stephan/Dokumente/core_dump_example/coredump)Refusing to dump core to tty (use shell redirection or specify — output).
这表明该进程被 SIGABRT 停止。这个视图中的堆栈跟踪不是很详细,因为它不包括函数名。然而,使用 coredumpctl debug,你可以简单地用调试器(默认为 GDB )打开转储文件。输入 bt( 回溯(backtrace)的缩写)可以得到更详细的视图:
Core was generated by `./coredump -c1'.Program terminated with signal SIGABRT, Aborted.#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:5050 return ret;(gdb) bt#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50#1 0x00007fc37a9aa8d9 in __GI_abort () at abort.c:79#2 0x00007fc37aa054af in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7fc37ab14f4b "%sn") at ../sysdeps/posix/libc_fatal.c:181#3 0x00007fc37aa0ca9c in malloc_printerr (str=str@entry=0x7fc37ab130e0 "free(): invalid pointer") at malloc.c:5339#4 0x00007fc37aa0e49c in _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:4173#5 0x000000000040120e in freeSomething(void*) ()#6 0x0000000000401401 in main ()
与后续帧相比,main() 和 freeSomething() 的内存地址相当低。由于共享对象被映射到虚拟地址空间末尾的区域,可以认为 SIGABRT 是由共享库中的调用引起的。共享对象的内存地址在多次调用之间并不是恒定不变的,所以当你看到多次调用之间的地址不同时,完全可以认为是共享对象。
堆栈跟踪显示,后续的调用源于 malloc.c,这说明内存的(取消)分配可能出了问题。
在源代码中,(即使没有任何 C 知识)你也可以看到,它试图释放一个指针,而这个指针并没有被内存管理函数返回。这导致了未定义的行为,并导致了 SIGABRT。
void freeSomething(void *ptr){ free(ptr);}int nTmp = 5;int *ptrNull = &nTmp;freeSomething(ptrNull);
systemd 的这个 coredump 工具可以在 /etc/systemd/coredump.conf 中配置。可以在 /etc/systemd/systemd-tmpfiles-clean.timer 中配置轮换清理转储文件。
你可以在其 手册页 中找到更多关于 coredumpctl 的信息。
用调试符号编译
打开 Makefile 并注释掉第 9 行的最后一部分。现在应该是这样的:
CFLAGS =-Wall -Werror -std=c 11 -g
-g 开关使编译器能够创建调试信息。启动应用程序,这次使用 -c2 开关。
./coredump -c2
你会得到一个浮点异常。在 GDB 中打开该转储文件:
coredumpctl debug
这一次,你会直接被指向源代码中导致错误的那一行:
Reading symbols from /home/stephan/Dokumente/core_dump_example/coredump…[New LWP 6218]Core was generated by `./coredump -c2'.Program terminated with signal SIGFPE, Arithmetic exception.#0 0x0000000000401233 in zeroDivide () at main.cpp:2929 nRes = 5 / nDivider;(gdb)
键入 list 以获得更好的源代码概览:
(gdb) list24 int zeroDivide(){25 int nDivider = 5;26 int nRes = 0;27 while(nDivider > 0){28 nDivider--;29 nRes = 5 / nDivider;30 }31 return nRes;32 }
使用命令 info locals 从应用程序失败的时间点检索局部变量的值:
(gdb) info localsnDivider = 0nRes = 5
结合源码,可以看出,你遇到的是零除错误:
nRes = 5 / 0
结论
了解如何处理转储文件将帮助你找到并修复应用程序中难以重现的随机错误。而如果不是你的应用程序,将核心转储转发给开发人员将帮助她或他找到并修复问题。
via: opensource.com
作者: Stephan Avenwedde 选题: lujun9972 译者: wxy 校对: wxy
本文由 LCTT 原创编译, Linux中国 荣誉推出
点击“了解更多”可访问文内链接
标准from 11 to 20主要新增特性'>C 标准from 11 to 20主要新增特性C is an ISO standardized programing language. There are different C standards:
- C 98- C 03- C 11 modern C - C 14 |- C 17 |- C 20 /
Everything starting with C 11 is referred to as “Modern C ”. These standards define the language in great technical detail. They also serve as manuals for C compiler writers. It is a mind-boggling set of rules and specifications. The C standards can be bought, or a draft version can be downloaded for free. These drafts closely resemble the final C standard. When C code can be successfully transferred and compiled on different platforms (machines or compilers), and when C implementation closely follows the standard, we say that the code is portable. This is often referred to as portable C .
The standards surrounded by braces represent the so-called “Modern C .” Each standard describes the language and introduces new language and library features. It may also introduce changes to the existing rules. We will describe notable features in each of these standards.
标准和新特性 | 解释或描述 |
C 11 | |
11.1 Automatic Type Deduction | auto |
11.2 Range-based Loops | for(auto el:range) |
11.3 Initializer Lists | {} |
11.4 Move Semantics | move |
11.5 Lambda Expressions | [](){} |
11.6 The constexpr Specifier | constexpr |
11.7 Scoped enumerators | enum class |
11.8 Smart Pointers | unique_ptr |
11.9 std::unordered_set | unordered_set |
11.10 std::unordered_map | unordered_map |
11.11 std::tuple | pair<>->tuple<> |
11.12 static_assert | constexpr |
11.13 Concurrency | thread(func) |
11.14 Deleted and Defaulted Functions | default, delete |
11.15 Type Aliases | using id = type |
C 14 | |
14.1 Binary Literals | 0b1010 |
14.2 Digits Separators | 123'456 |
14.3 Auto for Functions | auto func(); |
14.4 Generic Lambdas | auto lambdas |
14.5 std::make_unique | make_unique() |
C 17 | |
17.1 Nested Namespaces | n::m::p declaration |
17.2 Constexpr Lambdas | Constexpr Lambdas |
17.3 Structured Bindings | auto[] = arr |
17.4 std::filesystem | filesystem |
17.5 std::string_view | non-owning view |
17.6 std::any | any c = 123; |
17.7 std::variant | union->variant |
C 20 | |
20.1 Modules | export and import |
20.2 Concepts | template type requires |
20.3 Lambda Templates | []<>(){} |
20.4 [likely] and [unlikely] Attributes | if (choice) [[likely]] |
20.5 Ranges | ranges::sort(vec); |
20.6 Coroutines | co_await |
20.7 std::span | span<int> is = vec; |
20.8 Mathematical Constants | numbers::log2e |
1 C 11
C 11 is an ISO C standard, published in 2011. To compile for this standard, add the -std=c 11 flag to a command-line compilation string if compiling with g or clang. If using Visual Studio, choose Project / Options / Configuration Properties / C/C / Language / C Language Standard and choose C 11. New Visual Studio versions already support this standard out of the box.
11.1 Automatic Type Deduction
This standard introduces the auto keyword which deduces the type of the variable based on the variable’s initializer:
int main(){ auto mychar = 'A'; auto myint = 123 456; auto mydouble = 456.789;}
11.2 Range-based Loops
The range-based loops allow us to iterate over the range, such as C standard-library containers:
#include <iostream>#include <vector>int main(){ std::vector<int> v = { 10, 20, 40, 5, -20, 75 }; for (auto el : v) { std::cout << el << 'n'; }}
The range-based for loop is of the following form: for (type element : container). This is read as for each element in a container (do something).
11.3 Initializer Lists
Initializer lists, represented by braces { } allow us to initialize objects in a uniform way. We can initialize single objects:
int main(){ int x{ 123 }; int y = { 456 }; double d{ 3.14 };}
And containers:
#include <vector>int main(){ std::vector<int> v = { 1, 2, 3, 4, 5 };}
List initialization also prevents narrowing conversions. If we tried to initialize our integer object with a double value inside the initializer list, the compilation would fail:
int main(){ int x = { 123.45 }; // Error, does not allowing narrowing}
When initializing our objects, we should prefer initializer lists {} to old-style parentheses ().
11.4 Move Semantics
C 11 standard introduces the move semantics for classes. We can initialize our objects by moving the data from other objects. This is achieved through move constructors and move assignment operators. Both accept the so-called rvalue reference as an argument. Lvalue is an expression that can be used on the left-hand side of the assignment operation. rvalues are expressions that can be used on the right-hand side of an assignment. The rvalue reference has the signature of some_type&&. To cast an expression to an rvalue reference, we use the std::move function. A simple move constructor and move assignment signature are:
class MyClass{public: MyClass(MyClass&& otherobject) // move constructor { //implement the move logic here } MyClass& operator=(MyClass&& otherobject) // move assignment operator { // implement the copy logic here return *this; }};
11.5 Lambda Expressions
Lambda expressions are anonymous function objects. They allow us to write a short code snippet to be used as a standard-library function predicate. Lambdas have a capture list, marked by [ ] where we can capture local variables by reference or copy, parameter list with optional parameters marked with ( ), and a lambda body, marked with { }. An empty lambda looks like [] () {};. A simple example of counting only the even numbers in a set using the lambda as a predicate:
#include <iostream>#include <vector>#include <algorithm>int main(){ std::vector<int> v = { 1, 2, 3, 4, 5 }; auto counteven = std::count_if(std::begin(v), std::end(v), [](int x) {return x % 2 == 0; }); // lambda expression std::cout << "The number of even vector elements is: " << counteven;}
11.6 The constexpr Specifier
The constexpr specifier promises the variable or a function can be evaluated during compile-time. If the expression can not be evaluated during compile-time, the compiler emits an error:
int main(){ constexpr int n = 123; //OK, 123 is a compile-time constant // expression constexpr double d = 456.78; //OK, 456.78 is a compile-time constant // expression constexpr double d2 = d; //OK, d is a constant expression int x = 123; constexpr int n2 = x; //compile-time error // the value of x is not known during // compile-time}
11.7 Scoped Enumerators
Enumerator使用时(右值)需显式声明类作用域。
The C 11 standard introduces the scoped enumerators . Unlike the old enumerators, the scoped enumerators do not leak their names into the surrounding scope. Scoped enums have the following signature: enum class Enumerator_Name {value1, value2 etc} signature. A simple example of a scoped enum is:
enum class MyEnum{ myfirstvalue, mysecondvalue, mythirdvalue};int main(){ MyEnum myenum = MyEnum::myfirstvalue;}
11.8 Smart Pointers
智能指针使用类模板封装原生指针及类指针操作及在自定义析构函数中在适当的时机调用delete或delete[](包括申请的堆内存的所有权控制或引用计数)。
Smart pointers point to objects, and when the pointer goes out of scope, the object gets destroyed. This makes them smart in the sense that we do not have to worry about manual deallocation of allocated memory. The smart pointers do all the heavy lifting for us.
There are two kinds of smart pointers, the unique pointer with an std::unique_ptr signature and a shared pointer with an std::shared_ptr signature. The difference between the two is that we can have only one unique pointer pointing at the object. In contrast, we can have multiple shared pointers pointing at an object. When the unique pointer goes out of scope, the object gets destroyed, and the memory is deallocated. When the last of the shared pointers pointing at our object goes out of scope, the object gets destroyed. The memory gets deallocated.
A unique pointer example:
#include <iostream>#include <memory>int main(){ std::unique_ptr<int> p(new int{ 123 }); std::cout << *p;} // p goes out of scope here, the memory gets deallocated, the object gets // destroyed39
A unique pointer can not be copied, only moved. To have multiple shared pointers pointing at the same object, we would write:
#include <iostream>#include <memory>int main(){ std::shared_ptr<int> p1(new int{ 123 }); std::shared_ptr<int> p2 = p1; std::shared_ptr<int> p3 = p1;} // when the last shared pointer goes out of scope, the memory gets // deallocated39
Shared pointers can be copied. It is said they share ownership of the object. When the last shared pointer gets out of scope, the pointed-to object gets destroyed, and the memory gets deallocated.
11.9 std::unordered_set
std::set使用红黑树为底层数据结构,std::unordered_set以哈希映射实现物理存储。
The std::unordered_set is a container that allows for constant time insertion, searching, and removal of elements. This container is implemented as an array of buckets of linked lists. The hash value of each element is calculated (hashed), and the object is placed into an appropriate bucket based on the hash value. The object themselves are not sorted in any particular order. To define an unordered set, we need to include the header. Example:
#include <iostream>#include <unordered_set>int main(){ std::unordered_set<int> myunorderedset = { 1, 2, 5, -4, 7, 10 }; for (auto el : myunorderedset) { std::cout << el << 'n'; }}
The values are not sorted but are unique. To insert single or multiple values into an unordered_set, we use the .insert() member function:
#include <iostream>#include <unordered_set>int main(){ std::unordered_set<int> myunorderedset = { 1, 2, 5, -4, 7, 10 }; myunorderedset.insert(6); // insert a single value myunorderedset.insert({ 8, 15, 20 }); // insert multiple values for (auto el : myunorderedset) { std::cout << el << 'n'; }}
To delete a value from an unordered set, we use the .erase() member function:
#include <iostream>#include <unordered_set>int main(){ std::unordered_set<int> myunorderedset = { 1, 2, 5, -4, 7, 10 }; myunorderedset.erase(-4); // erase a single value for (auto el : myunorderedset) { std::cout << el << 'n'; }}
11.10 std::unordered_map
std::map使用红黑树为底层数据结构,std::unordered_map以哈希映射实现物理存储。
Similar to std::unordered_set, there is also an std::unordered_map , an unordered container of key-value pairs with unique keys. This container also allows for fast insertion, searching, and removal of elements. The container is also data is also implemented as buckets. What element goes into what bucket depends on the element’s key hash value. To define an unordered map, we include the header. Example:
#include <iostream>#include <unordered_map>int main(){ std::unordered_map<char, int> myunorderedmap = { {'a', 1}, {'b', 2}, {'c', 5} }; for (auto el : myunorderedmap) { std::cout << el.first << ' '<< el.second << 'n'; }}
Here we initialize an unordered map with key-value pairs. In the range-based for loop, we print both the key and the value. Map elements are pairs. Pairs have member functions .first for accessing a key and .second for accessing a value. To insert an element into a map we can use the member function .insert() member function:
#include <iostream>#include <unordered_map>int main(){ std::unordered_map<char, int> myunorderedmap = { {'a', 1}, {'b', 2}, {'c', 5} }; myunorderedmap.insert({ 'd', 10 }); for (auto el : myunorderedmap) { std::cout << el.first << ' '<< el.second << 'n'; }}
We can also use the map’s operator [] to insert an element. Normally, this operator is used to access an element value by key. However, if the key does not exist, the operator inserts a new element into the map:
#include <iostream>#include <unordered_map>int main(){ std::unordered_map<char, int> myunorderedmap = { {'a', 1}, {'b', 2}, {'c', 5} }; myunorderedmap['b'] = 4; // key exists, change the value myunorderedmap['d'] = 10; // key does not exist, insert the new element for (auto el : myunorderedmap) { std::cout << el.first << ' ' << el.second << 'n'; }}
11.11 std::tuple
std::pair是一个两个成员特殊命名(first, second)的类模板。
std::tuple是一个有多个数据成员,可以由get<>()访问的类模板。
While std::pair can hold only two values, the std::tuple wrapper can hold more than two values. To use tuples, we need to include the header. To access a certain tuple element , we use the std::get(tuple_name) function:
#include <iostream>#include <utility>#include <tuple>int main(){ std::tuple<char, int, double> mytuple = { 'a', 123, 3.14 }; std::cout << "The first element is: " << std::get<0>(mytuple) << 'n'; std::cout << "The second element is: " << std::get<1>(mytuple) << 'n'; std::cout << "The third element is: " << std::get<2>(mytuple) << 'n';}
We can create a tuple using the std::make_tuple function:
#include <iostream>#include <tuple>#include <string>int main(){ auto mytuple = std::make_tuple<int, double, std::string>(123, 3.14, "Hello World."); std::cout << "The first tuple element is: " << std::get<0>(mytuple) << 'n'; std::cout << "The second tuple element is: " << std::get<1>(mytuple) << 'n'; std::cout << "The third tuple element is: " << std::get<2>(mytuple) << 'n';}
Instead of typing a lengthy tuple type, which is std::tuple, we used the auto specifier to deduce the type name for us.
40.1.12 static_assert
The static_assert directive checks a static (constexpr) condition during compile time. If the condition is false, the directive fails the compilation and displays an error message. Example:
int main(){ constexpr int x = 123; static_assert(x == 456, "The constexpr value is not 456.");}
Here the static_assert checks if the value of x is equal to 456 during compile time. Since it is not, the compilation will fail with a "The constexpr value is not 456." message. We can think of the static_assert as a way of testing our code during compile time. It is also a neat way of testing if the value of a constexpr expression is what we expect it to be.
40.1.13 Introduction to Concurrency
C 11 standard introduces facilities for working with threads. To enable threading, we need to add the -pthreads flag when compiling with g and clang on the command line. Example:
g -std=c 11 -Wall -pthread source.cpp
With clang it will be:
clang -std=c 11 -Wall -pthread source.cpp
When we compile and link our source code program, an executable file is produced. When we start the executable, the program gets loaded into memory and starts running. This running program is called a process. When we start multiple executable files, we can have multiple processes. Each process has its memory, its own address space. Within a process, there can be multiple threads. What are the threads? Threads or threads of execution are an OS mechanism that allows us to execute multiple pieces of code concurrently/simultaneously.
For example, we can execute multiple functions concurrently using threads. In a broader sense, concurrently can also mean in parallel. A thread is part of the process. A process can spawn one or more threads. Threads share the same memory and thus can communicate with each other using this shared memory.
To create a thread object, we use the std::thread class template from a header file. Once defined, the thread starts executing. To create a thread that executes a code inside a function, we supply the function name to the thread constructor as a parameter. Example:
#include <iostream>#include <thread>void function1(){ for (int i = 0; i < 10; i ) { std::cout << "Executing function1." << 'n'; }}int main(){ std::thread t1{ function1 }; // create and start a thread t1.join(); // wait for the t1 thread to finish}
Here we have defined a thread called t1 that executes a function function1. We supply the function name to the std::thread constructor as a first parameter. In a way, our program now has a main thread, which is the main() function itself, and the t1 thread, which was created from the main thread. The .join() member function says: “hey, main thread, please wait for me to finish my work before continuing with yours.” If we left out the .join() function, the main thread would finish executing before the t1 thread has finished its work. We avoid this by joining the child thread to the main thread.
If our function accepts parameters, we can pass those parameters when constructing the std::thread object:
#include <iostream>#include <thread>#include <string>void function1(const std::string& param){ for (int i = 0; i < 10; i ) { std::cout << "Executing function1, " << param << 'n'; }}int main(){ std::thread t1{ function1, "Hello World from a thread." }; t1.join();}
We can spawn multiple threads in our program/process by constructing multiple std::thread objects. An example where we have two threads executing two different functions concurrently/simultaneously:
#include <iostream>#include <thread>void function1(){ for (int i = 0; i < 10; i ) { std::cout << "Executing function1." << 'n'; }}void function2(){ for (int i = 0; i < 10; i ) { std::cout << "Executing function2." << 'n'; }}int main(){ std::thread t1{ function1 }; std::thread t2{ function2 }; t1.join(); t2.join();}
This example creates two threads executing two different functions concurrently.
The function1 code executes in a thread t1, and the function2 code executes in a separate thread called t2.
We can also have multiple threads executing code from the same function concurrently:
#include <iostream>#include <thread>#include <string>void myfunction(const std::string& param){ for (int i = 0; i < 10; i ) { std::cout << "Executing function from a " << param << 'n'; }}int main(){ std::thread t1{ myfunction, "Thread 1" }; std::thread t2{ myfunction, "Thread 2" }; t1.join(); t2.join();}
Threads sometimes need to access the same object. In our example, both threads are accessing the global std::cout object in order to output the data. This can be a problem. Accessing the std::cout object from two different threads at the same time allows one thread to write a little to it, then another thread jumps in and writes a little to it, and we can end up with some strange text in the console window:
Executi.Executingng function1.Executing function2.
This means we need to synchronize the access to a shared std::cout object somehow. While one thread is writing to it, we need to ensure that the thread does not write to it.
We do so by locking and unlocking mutex-es. A mutex is represented by std::mutex class template from a header. A mutex is a way to synchronize access to shared objects between multiple threads. A thread owns a mutex once it locks the mutex, then performs access to shared data and unlocks the mutex when access to shared data is no longer needed. This ensures only one thread at the time can have access to a shared object, which is std::cout in our case.
Here is an example where two threads execute the same function and guard access to std::cout object by locking and unlocking mutexes:
#include <iostream>#include <thread>#include <string>#include <mutex>std::mutex m; // will guard std::coutvoid myfunction(const std::string& param){ for (int i = 0; i < 10; i ) { m.lock(); std::cout << "Executing function from a " << param << 'n'; m.unlock(); }}int main(){ std::thread t1{ myfunction, "Thread 1" }; std::thread t2{ myfunctiosn, "Thread 2" }; t1.join(); t2.join();}
We can forget to unlock the mutex manually. A better approach is to use the std::lock_guard function instead. It locks the mutex, and once it goes out of scope, it automatically unlocks the mutex. Example:
#include <iostream>#include <thread>#include <string>#include <mutex>std::mutex m; // will guard std::coutvoid myfunction(const std::string& param){ for (int i = 0; i < 10; i ) { std::lock_guard<std::mutex> lg(m); std::cout << "Executing function from a " << param << 'n'; } // lock_guard goes out of scope here and unlocks the mutex}int main(){ std::thread t1{ myfunction, "Thread 1" }; std::thread t2{ myfunction, "Thread 2" }; t1.join(); t2.join();}
11.14 Deleted and Defaulted Functions
If we do not supply a default constructor, the compiler will generate one for us so that we can write:
class MyClass{};int main(){ MyClass o; // OK, there is an implicitly defined default constructor}
However, in certain situations, the default constructor will not be implicitly generated. For example, when we define a copy constructor for our class, the default constructor is implicitly deleted. Example:
#include <iostream>class MyClass{public: MyClass(const MyClass& other) { std::cout << "Copy constructor invoked."; }};int main(){ MyClass o; // Error, there is no default constructor}
To force the instantiation of a default, compiler-generated constructor, we provide the =default specifier in its declaration. Example:
#include <iostream>class MyClass{public: MyClass() = default; // defaulted member function MyClass(const MyClass& other) { std::cout << "Copy constructor invoked."; }};int main(){ MyClass o; // Now OK, the defaulted default constructor is there MyClass o2 = o; // Invoking the copy constructor}
The =default specifier, when used on a member function, means: whatever the language rules, I want this default member function to be there. I do not want it to be implicitly disabled.
Similarly, if we want to disable a member function from appearing, we use the =delete specifier. To disable the copy constructor and copy assignment, we would write:
#include <iostream>class MyClass{public: MyClass() { std::cout << "Default constructor invoked."; } MyClass(const MyClass& other) = delete; // delete the copy constructor MyClass& operator=(const MyClass& other) = delete; // delete the copy // assignment operator};int main(){ MyClass o; // OK MyClass o2 = o; // Error, a call to deleted copy constructor MyClass o3; o3 = o; // Error, a call to deleted copy assignment operator}
These specifiers are mostly used in situations where we want to:
a. force or the instantiation of implicitly defined member functions such as constructors and assignment operators, when we use the =default; expression
b. disable the instantiation of implicitly defined member functions using the =delete; expression
These expressions can also be used on other functions as well.
115 Type Aliases
A type alias is a user-provided name for the existing type. If we want to use a different name for the existing type, we write: using my_type_name = existing_type_name; Example:
#include <iostream>#include <string>#include <vector>using MyInt = int;using MyString = std::string;using MyVector = std::vector<int>;int main(){ MyInt x = 123; MyString s = "Hello World"; MyVector v = { 1, 2, 3, 4, 5 };}
2 C 14
C 14 is an ISO C standard published in 2014. It brings some additions to the language and the standard library, but mainly complements and fixes the C 11 standard. When we say we want to use the C 11 standard, what we actually want is the C 14 standard. Below are some of the new features for C 14.
To compile for C 14, add the -std=c 14 flag to a command-line compilation string if using g or clang compiler. In Visual Studio, choose Project / Options / Configuration Properties / C/C / Language / C Language Standard and choose C 14.
14.1 Binary Literals
Values are represented by literals. So far, we have mentioned three different kinds of binary literals: decimal, hexadecimal, and octal as in the example below:
int main(){ int x = 10; int y = 0xA; int z = 012;}
These three variables have the same value of 10, represented by different number literals. C 14 standard introduces the fourth kind of integral literals called binary literals. Using binary literals, we can represent the value in its binary form. The literal has a 0b prefix, followed by a sequence of ones and zeros representing a value. To represent the number 10 as a binary literal, we write:
int main(){ int x = 0b101010;}
The famous number 42 in binary form would be:
int main(){ int x = 0b1010;}
Important to rememberValues are values; they are some sequence of bits and bytes in memory. What can be different is the value representation. There are decimal, hexadecimal, octal, and binary representations of the value. These different forms of the same thing can be relevant to us humans. To a machine, it is all bits and bytes, transistors, and electrical current.
14.2 Digits Separators
In C 14, we can separate digits with a single quote to make it more readable:
int main(){ int x =100'000'000;}
The compiler ignores the quotes. The separators are only here for our benefit, for example, to split a large number into more readable sections.
14.3 Auto for Functions
We can deduce the function type based on the return statement value:
auto myintfn() // integer{ return 123;}auto mydoublefn() // double{ return 3.14;}int main(){ auto x = myintfn(); // int auto d = mydoublefn(); // double}
14.4 Generic Lambdas
We can use auto parameters in lambda functions now. The type of the parameter will be deduced from the value supplied to a lambda function. This is also called a generic lambda :
#include <iostream>int main(){ auto mylambda = [](auto p) {std::cout << "Lambda parameter: " << p << 'n'; }; mylambda(123); mylambda(3.14);}
14.5 std::make_unique
C 14 introduces a std::make_unique function for creating unique pointers. It is declared inside a header. Prefer this function to raw new operator when creating unique pointers:
#include <iostream>#include <memory>class MyClass{private: int x; double d;public: MyClass(int xx, double dd) : x{ xx }, d{ dd } {} void printdata() { std::cout << "x: " << x << ", d: " << d; }};int main(){ auto p = std::make_unique<MyClass>(123, 456.789); p->printdata();}
3 C 17
The C 17 standard introduces new language and library features and changes some of the language rules.
17.1 Nested Namespaces
Remember how we said we could have nested namespaces ? We can put a namespace into another namespace. We used the following the nest namespaces:
namespace MyNameSpace1{ namespace MyNameSpace2 { namespace MyNameSpace3 { // some code } }}
The C 17 standard allows us to nest namespaces using the namespace resolution operator. The above example can now be rewritten as:
namespace MyNameSpace1::MyNameSpace2::MyNameSpace3{ // some code}
17.2 Constexpr Lambdas
Lambdas can now be a constant expression, meaning they can be evaluated during compile-time:
int main(){ constexpr auto mylambda = [](int x, int y) { return x y; }; static_assert(mylambda(10, 20) == 30, "The lambda condition is not true.");}
An equivalent example where we put the constexpr specifier in the lambda itself, would be:
int main(){ auto mylambda = [](int x, int y) constexpr { return x y; }; static_assert(mylambda(10, 20) == 30, "The lambda condition is not true.");}
This was not the case in earlier C standards.
17.3 Structured Bindings
Structured binding binds the variable names to elements of compile-time known expressions, such as arrays or maps. If we want to have multiple variables taking values of expression elements, we use the structured bindings. The syntax is:
auto [myvar1, myvar2, myvar3] = some_expression;
A simple example where we bound three variables to be aliases for three array elements would be:
int main(){ int arr[] = { 1, 2, 3 }; auto [myvar1, myvar2, myvar3] = arr;}
Now we have defined three integer variables. These variables have array elements values of 1, 2, 3, respectively. These variables are copies of array elements. Making changes to variables does not affect the array elements themselves:
#include <iostream>int main(){ int arr[] = { 1, 2, 3 }; auto [myvar1, myvar2, myvar3] = arr; myvar1 = 10; myvar2 = 20; myvar3 = 30; for (auto el : arr) { std::cout << el << ' '; }}
We can make structured bindings of reference type by using the auto& syntax. This means the variables are now references to array elements and making changes to variables also changes the array elements:
#include <iostream>int main(){ int arr[] = { 1, 2, 3 }; auto& [myvar1, myvar2, myvar3] = arr; myvar1 = 10; myvar2 = 20; myvar3 = 30; for (auto el : arr) { std::cout << el << ' '; }}
It is an excellent way of introducing and binding multiple variables to some container-like expression elements.
17.4 std::filesystem
The std::filesystem library allows us to work with files, paths, and folders on our system. The library is declared through a header. Paths can represent paths to files and paths to folders. To check if a given folder exists, we use:
#include <iostream>#include <filesystem>int main(){ std::filesystem::path folderpath = "C:MyFolder"; if (std::filesystem::exists(folderpath)) { std::cout << "The path: " << folderpath << " exists."; } else { std::cout << "The path: " << folderpath << " does not exist."; }}
Similarly, we can use the std::filesystem::path object to check if a file exists:
#include <iostream>#include <filesystem>int main(){ std::filesystem::path folderpath = "C:MyFoldermyfile.txt"; if (std::filesystem::exists(folderpath)) { std::cout << "The file: " << folderpath << " exists."; } else { std::cout << "The file: " << folderpath << " does not exist."; }}
To iterate over folder elements, we use the std::filesystem::directory_iterator iterator:
#include <iostream>#include <filesystem>int main(){ auto myfolder = "C:MyFolder"; for (auto el : std::filesystem::directory_iterator(myfolder)) { std::cout << el.path() << 'n'; }}
Here we iterate over the directory entries and print each of the elements full path using the .path() member function.
For Linux, we need to adjust the path and use the following instead:
#include <iostream>#include <filesystem>int main(){ auto myfolder = "MyFolder/"; for (auto el : std::filesystem::recursive_directory_iterator(myfolder)) { std::cout << el.path() << 'n'; }}
To iterate over folder elements recursively, we use the std::filesystem::recursive_directory_iterator. This allows us to iterate recursively over all subfolders in a folder. On Windows, we would use:
#include <iostream>#include <filesystem>int main(){ auto myfolder = "C:MyFolder"; for (auto el : std::filesystem::recursive_directory_iterator(myfolder)) { std::cout << el.path() << 'n'; }}
On Linux and similar OS-es, we would use the following path:
#include <iostream>#include <filesystem>int main(){ auto myfolder = "MyFolder/"; for (auto el : std::filesystem::directory_iterator(myfolder)) { std::cout << el.path() << 'n'; }}
Below are some useful utility functions inside the std::filesystem namespace:
std::filesystem::create_directory for creating a directory
std::filesystem::copy for copying files and directories
std::filesystem::remove for removing a file or an empty folder
std::filesystem::remove_all for removing folders and subfolders
17.5 std::string_view
引用一段字符串并保留长度信息。
Copying data can be an expensive operation in terms of CPU usage. Passing substrings as function parameters would require making a copy of substrings. This is a costly operation. The string_view class template is an attempt to rectify that.
The string_view is a non-owning view of a string or a substring. It is a reference to something that is already there in the memory. It is implemented as a pointer to some character sequence plus the size of that sequence. With this kind of structure, we can parse strings efficiently.
The std::string_view is declared inside the header file. To create a string_view from an existing string, we write:
#include <iostream>#include <string>#include <string_view>int main(){ std::string s = "Hello World."; std::string_view sw(s); std::cout << sw;}
To create a string_view for a substring of the first five characters, we use the different constructor overload. This string_view constructor takes a pointer to the first string element and the length of the substring:
#include <iostream>#include <string>#include <string_view>int main(){ std::string s = "Hello World."; std::string_view sw(s.c_str() , 5); std::cout << sw;}
Once we create a string_view, we can use its member functions. To create a substring out of a string_view, we use the .substr() member function. To create a substring, we supply the starting position index and length. To create a substring of the first five characters, we use:
#include <iostream>#include <string>#include <string_view>int main(){ std::string s = "Hello World"; std::string_view sw(s); std::cout << sw.substr(0, 5);}
A string_view allows us to parse (not change) the data that is already in the memory, without having to make copies of the data. This data is owned by another string or character array object.
17.6 std::any
The std::any container can hold a single value of any type. This container is declared inside the header file. Example:
可以理解为一种特殊的void*(void*在解引用前也需要类型显式转换为具体特定的类型)。
#include <any>int main(){ std::any a = 345.678; std::any b = true; std::any c = 123;}
To access the value of an std::any object in a safe manner, we cast it to a type of our choice using the std::any_cast function:
#include <iostream>#include <any>int main(){ std::any a = 123; std::cout << "Any accessed as an integer: " << std::any_cast<int>(a) << 'n'; a = 456.789; std::cout << "Any accessed as a double: " << std::any_cast<double>(a) << 'n'; a = true; std::cout << "Any accessed as a boolean: " << std::any_cast<bool>(a) << 'n';}
Important, the std::any_cast will throw an exception if we try to convert, for example, 123 to type double. This function performs only the type-safe conversions.Another std::any member function is .has_value() which checks if the std::any object holds a value:
#include <iostream>#include <any>int main(){ std::any a = 123; if (a.has_value()) { std::cout << "Object a contains a value." << 'n'; } std::any b{}; if (b.has_value()) { std::cout << "Object b contains a value." << 'n'; } else { std::cout << "Object b does not contain a value." << 'n'; }}
17.7 std::variant
There is another type of data in C called union. A union is a type whose data members of different types occupy the same memory. Only one data member can be accessed at a time. The size of a union in memory is the size of its largest data member. The data members overlap in a sense. To define a union type in C , we write:
union MyUnion{ char c; // one byte int x; // four bytes double d; // eight bytes};
Here we declared a union type that can hold characters or integers or doubles. The size of this union is the size of its largest data member double, which is probably eight bytes, depending on the implementation. Although the union declares multiple data members, it can only hold a value of one member at any given time. This is because all the data members share the same memory location. And we can only access the member that was the last written-to. Example:
#include <iostream>union MyUnion{ char c; // one byte int x; // four bytes double d; // eight bytes};int main(){ MyUnion o; o.c = 'A'; std::cout << o.c << 'n'; // accessing o.x or o.d is undefined behavior at this point o.x = 123; std::cout << o.c; // accessing o.c or o.d is undefined behavior at this point o.d = 456.789; std::cout << o.c; // accessing o.c or o.x is undefined behavior at this point}
C 17 introduces a new way of working with unions using the std::variant class template from a header. This class template offers a type-safe way of storing and accessing a union. To declare a variant using a std::variant, we would write:
#include <variant>int main(){ std::variant<char, int, double> myvariant;}
This example defines a variant that can hold three types. When we initialize or assign a value to a variant, an appropriate type is chosen. For example, if we initialize a variant with a character value, the variant will currently hold a char data member. Accessing other members at this point will throw an exception. Example:
#include <iostream>#include <variant>int main(){ std::variant<char, int, double> myvariant{ 'a' }; // variant now holds // a char std::cout << std::get<0>(myvariant) << 'n'; // obtain a data member by // index std::cout << std::get<char>(myvariant) << 'n'; // obtain a data member // by type myvariant = 1024; // variant now holds an int std::cout << std::get<1>(myvariant) << 'n'; // by index std::cout << std::get<int>(myvariant) << 'n'; // by type myvariant = 123.456; // variant now holds a double}
We can access a variant value by index using the std::get(variant_name) function. Or we can access the variant value by a type name using: std::get(variant_name). If we tried to access a wrong type or wrong index member, an exception of type const std::bad_variant_access& would be raised. Example:
#include <iostream>#include <variant>int main(){ std::variant<int, double> myvariant{ 123 }; // variant now holds an int std::cout << "Current variant: " << std::get<int>(myvariant) << 'n'; try { std::cout << std::get<double>(myvariant) << 'n'; // exception is // raised } catch (const std::bad_variant_access& ex) { std::cout << "Exception raised. Description: " << ex.what(); }}
We define a variant that can hold either int or double. We initialize the variant with a 123 literal of type int. So now our variant holds an int data member. We can access that member using the index of 0 or a type name which we supply to the std::get function. Then we try to access the wrong data member of type double. An exception is raised. And the particular type of that exception is std::bad_variant_access. In the catch block, we handle the exception by parsing the parameter we named ex. A parameter is of type std::bad_variant_access, which has a .what() member function that provides a short description of the exception.
20.4 C 20
The C 20 standard promises to bring some big additions to the language. Its impact on the existing standards is said to be as big as the C 11 was to a C 98/C 03 standard. At the time of writing, the C 20 standard is to be ratified around May 2020. The full implementation and the support in the compilers should follow. Some of the following things may, at first glance, seem intimidating, especially when beginning C . However, do not worry. At the time of writing, none of the compilers fully support the C 20 standard, but that is about to change. Once the compilers fully support the C 20 standard, trying out the examples will be much easier. With that in mind, let us go through some of the most exciting C 20 features.
20.1 Modules
Modules are the new C 20 feature, which aims to eliminate the need for the separation of code into header and source files. So far, in traditional C , we have organized our source code using headers files and source files. We keep our declarations/interfaces in header files. We put our definitions/implementations in source files. For example, we have a header file with a function declaration:
mylibrary.h
#ifndef MYLIBRARY_H#define MYLIBRARY_Hint myfunction();#endif // !MYLIBRARY_H39
Here we declare a function called myfunction(). We surround the code with header guards, which ensures the header file is not included multiple times during the compilation. And we have a source file with the function definition. This source file includes our header file:
// mylibrary.cpp#include "mylibrary.h"int myfunction(){ return 123;}
In our main.cpp file we also include the above header file and call the function:
// main.cpp#include "mylibrary.h"int main(){ int x = myfunction();}
We include the same header multiple times. This increases compilation time. Modules are included only once, and we do not have to separate the code into interface and implementation. One way is to have a single module file, for example, mymodule.cpp where we provide the entire implementation and export of this function.
To create a simple module file which implements and exports the above function, we write:
// mymodule.cppexport module mymodule;export int myfunction() { return 123; }
Explanation: the export module mymodule; line says there is a module called mymodule in this file. In the second line, the export specifier on the function means the function will be visible once the module is imported into the main program.
We include the module in our main program by writing the import mymodule; statement.
// main.cppimport mymodule;int main(){ int x = myfunction();}
In our main program, we import the module and call the exported myfunction() function.
A module can also provide an implementation but does need to export it. If we do not want our function to be visible to the main program, we will omit the export specifier in the module. This makes the implementation private to the module:
export module mymodule;export int myfunction() { return 123; }int myprivatefunction() { return 456; }
If we have a module with a namespace in it, and a declaration inside that namespace is exported, the entire namespace is exported. Within that namespace, only the exported functions are visible Example:
// mymodule2.cppexport module mymodule2;namespace MyModule{ export int myfunction() { return 123; }}
main2.cpp:
import mymodule2;int main(){ int x = MyModule::myfunction();}
20.2 Concepts
类型总是定义了一组特定的操作。模板实现的泛型有些不能满足特定类型的需要,一种方法是类型特化,concepts的方法是显式声明类型的特定要求。
Remember the class templates and function templates providing generic types T? If we want our template argument T to satisfy certain requirements, then we use concepts. In other words, we want our T to satisfy certain compile-time criteria. The signature for a concept is:
templateconcept concept_name = requires (T var_name) { reqirement_expression; };
The second line defines a concept name followed by a reserved word requires, followed by an optional template argument T and a local var_name, followed by a requirement_expression which is a constexpr of type bool.
In a nutshell, the concept predicate specifies the requirements a template argument must satisfy in order to be used in a template. Some of the requirements we can write ourselves, some are already pre-made.
We can say that concepts constrain types to certain requirements. They can also be seen as a sort of compile-time assertions for our template types.
For example, if we want a template argument to be incrementable by one, we will specify the concept for it:
template <typename T>concept MustBeIncrementable = requires (T x) { x = 1; };
To use this concept in a template, we write:
template<MustBeIncrementable T>void myfunction(T x){ // code goes in here}
Another way to include the concept into our template is:
template<typename T> requires MustBeIncrementable <T>void myfunction(T x){ // code goes in here}
A full working example would be:
#include <iostream>#include <concepts>template <typename T>concept MustBeIncrementable = requires (T x) { x ; };template<MustBeIncrementable T>void myfunction(T x){ x = 1; std::cout << x << 'n';}int main(){ myfunction<char>(42); // OK myfunction<int>(123); // OK myfunction<double>(345.678); // OK}
This concept ensures our argument x of type T must be able to accept operator , and the argument must be able to be incremented by one. This check is performed during the compile-time. The requirement is indeed true for types char, int, and double. If we used a type for which the requirement is not fulfilled, the compiler would issue a compile-time error.
We can combine multiple concepts. Let us, for example, have a concept that requires the T argument to be an even or an odd number.
template <typename T>concept MustBeEvenOrOdd = requires (T x) { x % 2; };
Now our template can include both the MustBeIncrementable and MustBeEvenOrOdd concepts:
template<typename T> requires MustBeIncrementable<T> && MustBeEvenNumber<T>;void myfunction(T x){ // code goes in here}
The keyword requires is used both for the expression in the concept and when including the concept into our template class/function.
The complete program, which includes both concept requirements, would be:
#include <iostream>#include <concepts>template <typename T>concept MustBeIncrementable = requires (T x) { x ; };template <typename T>concept MustBeEvenOrOdd = requires (T x) { x % 2; };template<typename T> requires MustBeIncrementable<T> && MustBeEvenOrOdd<T>void myfunction(T x){ std::cout << "The value conforms to both conditions: " << x << 'n';}int main(){ myfunction<char>(123); // OK myfunction<int>(124); // OK myfunction<double>(345); // Error, a floating point number is not even // nor odd}
In this example, the template will be instantiated if both concept requirements are evaluated to true during compile time. Only the myfunction(123); and myfunction(124); functions can be instantiated and pass the compilation. The arguments of types char and int are indeed incrementable and can be either even or odd. However, the statement myfunction(345); does not pass a compilation. The reason is that the second requirement MustBeEvenOrOdd is not fulfilled as floating-point numbers are neither odd nor even.
Important! Both concepts say: for every x of type T, the statement inside the code-block { } compiles and nothing more. It just compiles. If it compiles, the requirement for that type is fulfilled.
If we want our type T to have a member function, for example, .empty() and we want the result of that function to be convertible to type bool, we write:
template <typename T>concept HasMemberFunction requires (T x){ { x.empty() } -> std::convertible_to(bool);};
There are multiple predefined concepts in the C 20 standard. They check if the type fulfills certain requirements. These predefined concepts are located inside the header. Some of them are:
a. std::integral – specifies the type should be an integral type
b. std::boolean – specifies the type can be used as a boolean type
c. std::move_constructible – specifies that the object of a particular type can be constructed using the move semantics
d. std::movable – specifies that the object of a certain type T can be moved
e. std::signed_integral – says the type is both integral and is a signed integral
20.3 Lambda Templates
We can now use template syntax in our lambda functions . Example:
auto mylambda = []<typename T>(T param){ // code};
For example, to printout the generic type name, using a templated lambda expression, we would write:
#include <iostream>#include <vector>#include <typeinfo>int main(){ auto mylambda = []<typename T>(T param) { std::cout << typeid(T).name() << 'n'; }; std::vector<int> v = { 1, 2, 3, 4, 5 }; mylambda(v); // integer std::vector<double> v2 = { 3.14, 123.456, 7.13 }; mylambda(v2); // double}
20.4 [likely] and [unlikely] Attributes
If we know that some paths of execution are more likely to be executed than others, we can help the compiler optimize the code by placing attributes. We use the [[likely]] attribute before the statement that is more likely to be executed. We can also put the [[unlikely]] attribute before the statement that is unlikely to be executed. For example, the attributes can be used on case branches inside the switch statement:
#include <iostream>void mychoice(int i){ switch (i) { [[likely]] case 1: std::cout << "Likely to be executed."; break; [[unlikely]] case 2: std::cout << "Unlikely to be executed."; break; default: break; }}int main(){ mychoice(1);}
If we want to use these attributes on the if-else branches, we write:
#include <iostream>int main(){ bool choice = true; if (choice) [[likely]] { std::cout << "This statement is likely to be executed."; } else [[unlikely]] { std::cout << "This statement is unlikely to be executed."; }}
20.5 Ranges
A range, in general, is an object that refers to a range of elements. The new C 20 ranges feature is declared inside a header. The ranges themselves are accessed via the std::ranges name. With classic containers such as an std::vector, if we want to sort the data, we would use:
#include <iostream>#include <vector>#include <algorithm>int main(){ std::vector<int> v = { 1, 2, 3, 4, 5 }; std::sort(v.begin(), v.end()); for (auto el : v) { std::cout << el << 'n'; }}
The std::sort function accepts vector’s .begin() and end() iterators. With ranges, it is much simpler, we just provide the name of the range, without iterators:
#include <iostream>#include <ranges>#include <vector>#include <algorithm>int main(){ std::vector<int> v = { 3, 5, 2, 1, 4 }; std::ranges::sort(v); for (auto el : v) { std::cout << el << 'n'; }}
Ranges have a feature called adaptors. One of the range adaptors is views. The views adaptors are accessed via std::ranges::views. Views are not owning. They cannot change the values of the underlying elements. It is also said they are lazily executed. This means the code from the views adaptors will not be executed until we iterate over the result of such views.
Let us create an example which uses range views to filter-out even numbers and print only the odd numbers from a vector by creating a range view:
#include <iostream>#include <ranges>#include <vector>#include <algorithm>int main(){ std::vector<int> v = { 1, 2, 3, 4, 5 }; auto oddnumbersview = v | std::views::filter([](int x) { return x % 2 == 1; }); for (auto el : oddnumbersview) { std::cout << el << 'n'; }}
Explanation: we have a simple vector with some elements. Then we create a view range adaptor on that vector, which filters the numbers in the range. For this, we use the pipe operator |. Only the numbers for which the predicate is true are included. In our case, this means the even numbers are excluded. Then we iterate over the filtered view and print out the elements.
Important to note, the underlying vector’s elements are unaffected as we are operating on a view, not on a vector.
Let us create an example which creates a view that returns only numbers greater than 2:
#include <iostream>#include <ranges>#include <vector>#include <algorithm>int main(){ std::vector<int> v = { 1, 2, 3, 4, 5 }; auto greaterthan2view = v | std::views::filter([](int x) { return x > 2; }); for (auto el : greaterthan2view) { std::cout << el << 'n'; }}
Now, let us combine the two views into one big view by separating them with multiple pipe | operators:
#include <iostream>#include <ranges>#include <vector>#include <algorithm>int main(){ std::vector<int> v = { 1, 2, 3, 4, 5 }; auto oddandgreaterthan2 = v | std::views::filter([](int x) { return x % 2 == 1; }) | std::views::filter([](int x) { return x > 2; }); for (auto el : oddandgreaterthan2) { std::cout << el << 'n'; }}
This example creates a view range adaptor containing odd numbers greater than two. We create this view by combining two different range views into one.
Another ranges adaptors are algorithms. The idea is to have the algorithms overload for ranges. To call an algorithm adaptor we use: std::ranges::algorithm_name(parameters). Example using the std::ranges::reverse() algorithm:
#include <iostream>#include <ranges>#include <vector>#include <algorithm>int main(){ std::vector<int> v = { 1, 2, 3, 4, 5 }; std::ranges::reverse(v); for (auto el : v) { std::cout << el << 'n'; }}
Unlike views, the ranges algorithms modify the actual vector content.
40.4.6 Coroutines
A coroutine is a function that can be suspended and be resumed. The ordinary function is a coroutine if it uses any of the following operators in its function body:
a. co_await – suspends the execution of the coroutine until some other computation is performed, that is until the coroutine itself resumes
b. co_yield – suspends a coroutine and return a value to the caller
c. co_return – returns from a coroutine and stops its execution
20.7 std::span
std::string_view定义的是特定的类型(string),std::span是与std:string_view相同概念的全部类型的应用。
Some containers and types store their elements in a sequence, one next to the other. This is the case for arrays and vectors. We can represent such containers with a pointer to their first element plus the length of the container. A std::span class template from a header is just that. A reference to a span of contiguous container elements. One reason to use the std::span, is that it is cheap to construct and copy. Span does not own a vector or an array it references. However, it can change the value of the elements. To create a span from a vector we use:
#include <iostream>#include <vector>#include <span>int main(){ std::vector<int> v = { 1, 2, 3 }; std::span<int> myintspan = v; myintspan[2] = 256; for (auto el : v) { std::cout << el << 'n'; }}
Here, we created a span that references vector elements. Then we used the span to change the vector’s third element. With span, we do not have to worry about passing a pointer and a length around, and we just use the neat syntax of a span wrapper. Since the size of the vector can change, we say our span has a dynamic extent . We can create a fixed-size span from a fixed-sized array. We say our span now has a static extent. Example:
#include <iostream>#include <span>int main(){ int arr[] = { 1, 2, 3, 4, 5 }; std::span<int, 5> myintspan = arr; myintspan[4] = 10; for (auto el : arr) { std::cout << el << 'n'; }}
20.8 Mathematical Constants
C 20 standard introduces a way to represent some of the mathematical constants. To use them, we need to include the header. The constants themselves are inside the std::numbers namespace. The following example shows how to use numbers pi and e, results of logarithmic functions and square roots of numbers 2 and 3:
#include <iostream>#include <numbers>int main(){ std::cout << "Pi: " << std::numbers::pi << 'n'; std::cout << "e: " << std::numbers::e << 'n'; std::cout << "log2(e): " << std::numbers::log2e << 'n'; std::cout << "log10(e): " << std::numbers::log10e << 'n'; std::cout << "ln(2): " << std::numbers::ln2 << 'n'; std::cout << "ln(10): " << std::numbers::ln10 << 'n'; std::cout << "sqrt(2): " << std::numbers::sqrt2 << 'n'; std::cout << "sqrt(3): " << std::numbers::sqrt3 << 'n';}
ref:
Slobodan Dmitrović 《Modern C for Absolute Beginners》
-End-