分类
未分类

线程通讯

线程通讯

线程与线程之间通信,除了直接访问共享变量之外,还有一种更安全的方式,那就是消息循环。

建立线程消息循环

除了每个窗口都有一个消息循环以外,每个线程也都可以有一个消息循环。但是需要注意的是,为了节约资源,线程默认情况下不会建立消息循环,因为不是所有线程都会去接收消息。但是,如果你在线程函数中调用获取消息的函数的话,系统就会自动为线程创建消息循环。

总的来说,创建一个线程的消息循环,格式大致如下:

“`C++
DWORD WINAPI 线程函数(LPVOID lpParam)
{
// 一些初始化操作

<pre><code>// 调用 PeekMessage 去让系统建立消息循环
// 因为参数的最后一个传入的是 PM_NOREMOVE,所以原来的消息依然存在消息循环中
// 这里并不去判断消息,只是为了建立消息循环
MSG msg;
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
// 下面是消息循环主体
while (GetMessage(&msg, NULL, NULL, NULL))
{
switch (msg.message)
{
case 消息1:
消息1的处理;
break;
case 消息2:
消息2的处理;
break;
// 更多的消息处理
default:
// 和窗口消息循环不一样,线程消息循环可以没有默认消息处理
}
}
// 清理操作
return 0;
</code></pre>

}

<pre class="line-numbers prism-highlight" data-start="1"><code class="language-null"><br />&lt;p&gt;一旦你的线程函数执行在消息循环中,就会一直等待有人给这个线程发消息,直到线程收到了 &lt;code&gt;WM_QUIT&lt;/code&gt; 消息之后,线程才会退出。&lt;/p&gt;

&lt;p&gt;注意,当 &lt;code&gt;GetMessage()&lt;/code&gt; 获取到了 &lt;code&gt;WM_QUIT&lt;/code&gt; 消息的时候,就会返回 &lt;code&gt;FALSE&lt;/code&gt;。&lt;/p&gt;

&lt;h2&gt;向线程发消息&lt;/h2&gt;

&lt;p&gt;那么,怎样从另一个线程给某个指定的线程发消息呢?发送消息需要用到 &lt;code&gt;PostThreadMessage()&lt;/code&gt; 函数,这个函数的用法如下:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C++
BOOL PostThreadMessage(
DWORD idThread, // 目标线程的 ID
UINT Msg, // 要发送的消息
WPARAM wParam, // 消息的附加参数1
LPARAM IParam // 消息的附加参数2
);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这个函数的第一个参数,目标线程的ID 怎么获取呢?当我们在创建线程的时候,&lt;code&gt;CreateThread()&lt;/code&gt; 这个函数的最后一个参数返回的就是线程的ID,所以,传入这个ID就可以了。例如:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C++
DWORD id;
CreateThread(NULL, 0, fun, NULL, NULL, &amp;id);
PostThreadMessage(id, 消息, 0, 0);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;另外需要注意的一点是,因为线程执行时机的不确定性,当你调用 &lt;code&gt;PostThreadMessage()&lt;/code&gt; 的时候目标线程的消息循环可能还没建立,这个时候这个函数就会返回 FALSE。所以,你在发送线程消息的时候,需要检查一下这个函数的返回值,如果发送失败了,就需要重新发送这个消息。例如,当你确认目标线程消息循环是可用的时候,可以这样发送消息:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C++
while (!PostThreadMessage(id, 消息, 0, 0));&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;定义消息&lt;/h2&gt;

&lt;p&gt;如果你希望让你的线程退出,只需要发送一个 &lt;code&gt;WM_QUIT&lt;/code&gt; 消息就可以了。例如:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C++
PostThreadMessage(id, WM_QUIT, 0, 0);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;你可以向线程发送Windows定义的消息,也可以发送自定义的消息。可以看到,消息的变量类型是 &lt;code&gt;UINT&lt;/code&gt;,也就是说,任何正整数都可以当成消息发送。&lt;/p&gt;

&lt;p&gt;Windows 预定义了很多消息(1024条消息以内),如果你希望向线程发消息的话,使用的数字要大于 1024。微软将 &lt;code&gt;WM_USER&lt;/code&gt; 定义成了 1024,所以,你要定义自己的消息的话,可以这么定义:&lt;/p&gt;

&lt;p&gt;“`C++

<h1>define WM_MY_MESSAGE1 (WM_USER + 1)</h1>

<h1>define WM_MY_MESSAGE2 (WM_USER + 2)</h1>


下面给出一个例子,这个例子要实现:
1. 建议一个线程,线程建立一个消息循环;
2. 主线程给新建线程发送自定义的打印Hello world消息之后,新建线程输出Hello world

“`C++

define WM_HELLO_WORLD (WM_USER + 10086)

DWORD WINAPI fun(LPVOID lpParam)
{
MSG msg;
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);

while (GetMessage(&msg, NULL, NULL, NULL))
{
    switch (msg.message)
    {
    case WM_HELLO_WORLD:
        printf("Hello wolrd!");
        break;
    }
}

return 0;

}

int main()
{
DWORD idThread;
HANDLE hThread = CreateThread(NULL, 0, fun, NULL, NULL, &idThread);

while(!PostThreadMessage(idThread, WM_HELLO_WORLD, 0, 0))
{
    Sleep(100);
}

PostThreadMessage(idThread, WM_QUIT, 0, 0);
WaitForSingleObject(hThread, INFINITE);

return 0;

}

分类
dpdk analysis

dpdk 线程管理之 用户输入参数解析

在dpdk程序中,最开始都需要调用rte_eal_init 函数用来初始化dpdk环境,在rte_eal_init函数中用来初始化线程的函数是 rte_eal_cpu_init,该函数主要是探测 当且设备上有哪些cpu可用。 该函数 遍历设备上所有的cpu,并检测cpu是否可用。并将探测结果记录在lcore_config中以备用。

for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
            lcore_config[lcore_id].core_index = count;

            /* init cpuset for per lcore config */
            CPU_ZERO(&lcore_config[lcore_id].cpuset);

            /* in 1:1 mapping, record related cpu detected state */
            lcore_config[lcore_id].detected = eal_cpu_detected(lcore_id);//通过判断文件/sys/devices/system/cpu/cpu%u/topology/core_id文件是否存在判断cpu是否可用 可用返回1 不可用返回0
            if (lcore_config[lcore_id].detected == 0) {
                    config->lcore_role[lcore_id] = ROLE_OFF;
                    lcore_config[lcore_id].core_index = -1;
                    continue;
            }

            /* By default, lcore 1:1 map to cpu id */
            CPU_SET(lcore_id, &lcore_config[lcore_id].cpuset);//设置cpuset,1:1设置,线程只能跑在一个核上

            /* By default, each detected core is enabled */
            config->lcore_role[lcore_id] = ROLE_RTE;
            lcore_config[lcore_id].core_id = eal_cpu_core_id(lcore_id);//通过读取/sys/devices/system/cpu/cpu%u/topology/core_id文件内容获取当前指定CPU的core_id
            lcore_config[lcore_id].socket_id = eal_cpu_socket_id(lcore_id);//通过判断/sys/device/system/node文件家中有没有CPUcore_id的文件判断 CPU属于哪个 node
            if (lcore_config[lcore_id].socket_id >= RTE_MAX_NUMA_NODES) //如果获取的socket id 大于系统允许的,则有两种可能,第一是系统出错了,第二是系统没有开启socket。
#ifdef RTE_EAL_ALLOW_INV_SOCKET_ID
                    lcore_config[lcore_id].socket_id = 0;
#else
                    rte_panic("Socket ID (%u) is greater than "
                            "RTE_MAX_NUMA_NODES (%d)\n",
                            lcore_config[lcore_id].socket_id,
                            RTE_MAX_NUMA_NODES);
#endif

            RTE_LOG(DEBUG, EAL, "Detected lcore %u as "
                            "core %u on socket %u\n",
                            lcore_id, lcore_config[lcore_id].core_id,
                            lcore_config[lcore_id].socket_id);
            count++;
    }
config->lcore_count = count; //将可用核数量记录在config数据结构中

接下来,通过 eal_parse_args 获取用户的输入参数,设置启动的线程数,及使用那些核心。eal_parse_args 调用 eal_parse_common_option来解析参数。主要解析一下几个参数

c:可运行的核的16进制 bitmask调用eal_parse_cormask 解析 。关键代码如下

for (i = i - 1; i >= 0 && idx < RTE_MAX_LCORE; i--) { //遍历所有核心
            c = coremask[i];
            if (isxdigit(c) == 0) {//将16进制字符转换成16进制数
                    /* invalid characters */
                    return -1;
            }
            val = xdigit2val(c);
            for (j = 0; j < BITS_PER_HEX && idx < RTE_MAX_LCORE; j++, idx++)//每一位表示4个cpu的使用情况BITS_PER_HEX等于4.
            {
                    if ((1 << j) & val) { //如果不等于0 表示这个cpu需要使用
                            if (!lcore_config[idx].detected) { //如果cpu不存在则报错退出
                                    RTE_LOG(ERR, EAL, "lcore %u "
                                            "unavailable\n", idx);
                                    return -1;
                            }
                            cfg->lcore_role[idx] = ROLE_RTE;  //该核使用
                            lcore_config[idx].core_index = count; //存在则记录号,并保存
                            count++;
                    } else {
                            cfg->lcore_role[idx] = ROLE_OFF; //该核不使用
                            lcore_config[idx].core_index = -1;
                    }
            }
    }

l:指定可运行核的编号,处理函数是eal_parse_corelist 关键代码如下

 min = RTE_MAX_LCORE;
    do {
            while (isblank(*corelist))
                    corelist++;
            if (*corelist == '\0')
                    return -1;
            errno = 0;
            idx = strtoul(corelist, &end, 10); //将字符串转换成无符号整型
            if (errno || end == NULL)
                    return -1;
            while (isblank(*end))
                    end++;
            if (*end == '-') { //如果最后一个不能转换的是-说明指定的是一个序列
                    min = idx;
            } else if ((*end == ',') || (*end == '\0')) {
                    max = idx;
                    if (min == RTE_MAX_LCORE) //如果min是初始值,说明是一个数,将min等于max
                            min = idx;
                    for (idx = min; idx <= max; idx++) {
                            if (cfg->lcore_role[idx] != ROLE_RTE) {
                                    cfg->lcore_role[idx] = ROLE_RTE; //设置核可用
                                    lcore_config[idx].core_index = count;//记录核的序号
                                    count++;
                            }
                    }
                    min = RTE_MAX_LCORE;
            } else
                    return -1;
            corelist = end + 1;
    } while (*end != '\0');

–lcores COREMAP:指定工作线程可以工作在多个核上。解释如下:eal_parse_lcore函数处理。

/*
* The format pattern: --lcores='<lcores[@cpus]>[<,lcores[@cpus]>...]'
* lcores, cpus could be a single digit/range or a group.
* '(' and ')' are necessary if it's a group.
* If not supply '@cpus', the value of cpus uses the same as lcores.
* e.g. '1,2@(5-7),(3-5)@(0,2),(0,6),7-8' means start 9 EAL thread as below
*   lcore 0 runs on cpuset 0x41 (cpu 0,6)
*   lcore 1 runs on cpuset 0x2 (cpu 1)
*   lcore 2 runs on cpuset 0xe0 (cpu 5,6,7)
*   lcore 3,4,5 runs on cpuset 0x5 (cpu 0,2)
*   lcore 6 runs on cpuset 0x41 (cpu 0,6)
*   lcore 7 runs on cpuset 0x80 (cpu 7)
*   lcore 8 runs on cpuset 0x100 (cpu 8)

do {
            while (isblank(*lcores))
                    lcores++;
            if (*lcores == '\0')
                    goto err;

            lflags = 0;

            /* record lcore_set start point */
            lcore_start = lcores;

            /* go across a complete bracket */
            if (*lcore_start == '(') {
                    lcores += strcspn(lcores, ")");
                    if (*lcores++ == '\0')
                            goto err;
            }

            /* scan the separator '@', ','(next) or '\0'(finish) */
            lcores += strcspn(lcores, "@,");

            if (*lcores == '@') {
                    /* explicit assign cpu_set */
                    offset = eal_parse_set(lcores + 1, set, RTE_DIM(set)); //获取绑定的cpu序列
                    if (offset < 0)
                            goto err;

                    /* prepare cpu_set and update the end cursor */
                    if (0 > convert_to_cpuset(&cpuset,
                                              set, RTE_DIM(set)))
                            goto err;
                    end = lcores + 1 + offset;
            } else { /* ',' or '\0' */
                    /* haven't given cpu_set, current loop done */
                    end = lcores;

                    /* go back to check <number>-<number> */
                    offset = strcspn(lcore_start, "(-");
                    if (offset < (end - lcore_start) &&
                        *(lcore_start + offset) != '(')
                            lflags = 1;
            }

            if (*end != ',' && *end != '\0')
                    goto err;

            /* parse lcore_set from start point */
            if (0 > eal_parse_set(lcore_start, set, RTE_DIM(set)))//获取线程序列
                    goto err;

            /* without '@', by default using lcore_set as cpu_set */
            if (*lcores != '@' &&
                0 > convert_to_cpuset(&cpuset, set, RTE_DIM(set)))
                    goto err;

            /* start to update lcore_set */
            for (idx = 0; idx < RTE_MAX_LCORE; idx++) {
                    if (!set[idx])
                            continue;

                    if (cfg->lcore_role[idx] != ROLE_RTE) {
                            lcore_config[idx].core_index = count;//标记线程号
                            cfg->lcore_role[idx] = ROLE_RTE;//设置线程可用
                            count++;
                    }

                    if (lflags) {
                            CPU_ZERO(&cpuset);
                            CPU_SET(idx, &cpuset);
                    }
                    rte_memcpy(&lcore_config[idx].cpuset, &cpuset,
                               sizeof(rte_cpuset_t));//设置线程的 CPUSET
            }

            lcores = end + 1;
    } while (*end != '\0');

–master-lcore ID:设置主线程工作的核。处理函数是eal_parse_master_lcore 关键代码如下

cfg->master_lcore = (uint32_t) strtol(arg, &parsing_end, 0); //设置主工作线程的核
    if (errno || parsing_end[0] != 0)
            return -1;
    if (cfg->master_lcore >= RTE_MAX_LCORE)
            return -1;
    master_lcore_parsed = 1;
分类
未分类

reStructuredText语法详解

 

空白字符

缩进建议采用空格,但是Tab键也是可以的。在预处理文档的时候,Tab键会按照8个空格的长度来进行转换。

其他的一些空白字符(ASCII码为11和12的字符)会简单的在预处理的时候转换成单个空格。

空行

空行的作用就是将段落和其它元素分隔开来。多个连续的空行和单个空行是等效的,但是有一种情况例外,那就是在文字块的时候(其实,文字块中所有空白都会原样保留)。有些标记结合缩进的话,不用空行隔开也不会有歧义,这个时候就可以省略空行。一个文档的第一行文字前面可以不加空行,最后一行文字后面也可以不加空行。

缩进

缩进是唯一用来标记快引用、定义和内部嵌套内容的格式。这些内容包括:

  • 列表项的内容(多行列表项,单个列表项包含多项内容,包括嵌套列表);
  • 文字块的内容;
  • 显示标记块内容。

可能你对上面这些内容具体指什么还不了解,继续往下看就知道了。

任何一块文本的缩进只要小于当前的缩进级别(即,未缩进文本或取消缩进),就会将当前的缩进级别终结。

由于所有的缩进都是有意义的,所以文本的缩进级别必须一致。比如,缩进是块引用的唯一标记指示符:

This is a top-level paragraph.

 

This paragraph belongs to a first-level block quote.

 

Paragraph 2 of the first-level block quote.

在一个引用块中使用多个缩进级别的话,会产生更复杂的结构:

This is a top-level paragraph.

 

This paragraph belongs to a first-level block quote.

 

This paragraph belongs to a second-level block quote.

 

Another top-level paragraph.

 

This paragraph belongs to a second-level block quote.

 

This paragraph belongs to a first-level block quote.  The

second-level block quote above is inside this first-level

block quote.

如果一段文字由很多行组成,那么这些行必须左对齐:

This is a paragraph.  The lines of

this paragraph are aligned at the left.

 

This paragraph has problems.  The

lines are not left-aligned.  In addition

to potential misinterpretation, warning

and/or error messages will be generated

by the parser.

有一些结构的开始位置是一个特殊的标记符号,那么结构体内容部分的对齐方式和标记符号有关。使用简单标记的结构(无序列表,有序列表,脚注,站点标注,链接位置,指令,注释),缩进对齐的位置未内容部分的第一行的第一个字符的位置。例如,无序列表内容体部分至少需要对齐在2列的位置上,具体对齐位置和内容部分的左边缘位置一致:

  • This is the first line of a bullet list

item’s paragraph.  All lines must align

relative to the first line.  [1]_

 

This indented paragraph is interpreted

as a block quote.

 

Because it is not sufficiently indented,

this paragraph does not belong to the list

item.

 

.. [1] Here’s a footnote.  The second line is aligned

with the beginning of the footnote label.  The “..”

marker is what determines the indentation.

对于一些使用复杂标记符的结构(字段列表,选项列表),由于标记符可能包含任意文本,标记符下面的第一行的缩进位置将由内容的左边缘位置决定。例如,字段列表可能存在非常长的标记符(因为包含了字段名):

:Hello: This field has a short field name, so aligning the field

body with the first line is feasible.

 

:Number-of-African-swallows-required-to-carry-a-coconut: It would

be very difficult to align the field body with the left edge

of the first line.  It may even be preferable not to begin the

body on the same line as the marker.

转义字符

任何用来做标记的字符都可能在书面文本中有着其它诸多的含义。所以,一些已经用作标记的字符可能会不作为标记而出现在文本中。任何严格的标记系统都会提供一套转义字符来处理这种标记冲突。在reStructuredText中,和很多常见的语言类似,转义字符使用反斜杠。

反斜杠后面出现任何字符(除了非URI中的空格)都代表转义那个字符。被转移的字符仅代表该字符本身,不会作为标记被解释器解释。反斜杠会在输出的时候被移除。想要表示反斜杠的话,只需要连着写两个反斜杠就可以了。

在非URI内容中,反斜杠加空格将什么都不输出,它用于字符级别的内联标记。

在URI中,反斜杠加空格表示一个空格。

有两种情况下,反斜杠不代表任何意义:文字块和内联文字。在这些内容中,一个反斜杠仅代表反斜杠本身,不需要连续写两个反斜杠。

需要注意的是,reStructuredText解析器并不会去管自己解析的内容是从哪里提取出来的。反斜杠以及一些其它需要转义的字符在这种情况下必须要正确处理。比如,Python使用反斜杠来转义字符串中的一些特殊字符,但在其它环境下却不是。最简单的一个解决方案如下:

r”””This is a raw docstring.  Backslashes () are not touched.”””

引用名称

一个简单的引用名称由字母,数字,标准连字符(不能连着两个),下划线,句号,冒号和加号组成;空白字符和其它任何字符都不行。脚注标签,引用标签,解释文本角色和一些超链接引用使用简单引用名称语法。

使用标点或者短语(两个以上由空格分隔的单词)作为引用名称的方式叫做“短语引用”。短语引用需要用反引号将引用短语括起来:

Want to learn about my favorite programming language_?

 

.. _my favorite programming language: http://www.python.org

当然,简单引用名称也可以用反引号引起来。

引用名称是大小写不敏感的,并且不区分空白字符的数量。下面给出了内部处理引用名称的方法:

  • 标准化空白字符(一个或多个空格,横向或纵向制表符,换行符,回车符,或者制表符,都被解释成单个空格);
  • 大小写被标准化(所有字母都被转换成小写)。

例如,下面的超链接引用是等价的:

  • A HYPERLINK_

  • a    hyperlink_

  • `A

Hyperlink`_

超链接,脚注和引用标注共享同一个命名空间。这意味着他们之间能够互相引用同一个引用名称。当然,不同的引用类型的显示结果可能是不一样的。在某些情况下要小心引用名称冲突。

文档结构

文档

文档树元素:document。

一个解析后的reStructuredText文档的顶层元素是“document”元素。在初始化解析器之后,document元素只是一个简单的包含文档片段的容器,里面包括body元素,过渡元素和章节元素,但是缺乏文档标题或其他书目元素。

特别需要指出的,在reST中没有一个明确的方法来指明一个文档的标题或者子标题。然而,可以将一个单独的顶层节点标题作为文档标题。类似的,一个单独的紧跟在文档标题后面的2级标题可以作为文档的子标题。剩下的标题可以依次往上提升一到两个大纲级别。

章节

文档树元素:section,title。

章节通过它们的标题来定义。标题是通过在标题文字下加“下划线”来定义的,或者是在标题文字的上下加上“上下线”定义的。所谓的“上下线”其实就是一个标点符号从第1列开始重复多次,重复的次数必须至少要到达标题文字的右边界。特别需要指出的是,这个标点符号可以是任何非字母数字的7位ASCII字符。即,“! ” # $ % & ‘ ( ) * + , – . / : ; < = > ? @ [ \ ] ^ _ { | } ~”这些字符都是允许的,但是建议使用“= - : . ‘ ” ~ ^ _ * + #”这些字符。当使用上下标线的时候,上下标线使用的字符和长度必须一致。章节标题可以有很多的大纲级别,即使是输出格式有限制(HTML只有6个大纲级别)。

哪个标题样式代表哪个级别的标题,和标题出现的次序有关。第一个出现的标题样式会被当作最外层的标题(就像HTML中的h1),第二个出现的会被当作标题2,第三个出现的是标题3,等等。

下面是一些标题的例子:

===============

Section Title

===============

 


Section Title


 

Section Title

=============

 

Section Title


 

Section Title

““““““`

 

Section Title

””””””’

 

Section Title

………….

 

Section Title

~~~~~~~~~~~~~

 

Section Title


 

Section Title

+++++++++++++

 

Section Title

^^^^^^^^^^^^^

当一个标题同时拥有上下线的时候,最好上下线都比标题长一些,但是这仅仅是为了美观,而不影响语法。

标题后面的空行是可选的,直到遇到下一个标题,这之间的所有文本都属于这一节。

并不是所有的标题样式都必须要用到,也没有任何一种指定的标题样式必须要用到。但是,在文档中,标题样式的使用必须一致。一旦某个标题样式建立起来了,以后同级的标题就必须都用同一个样式。

每一个章节标题都会自动建立到自己的链接。引用标题的引用名称和标题一致。

章节可以包含过渡元素,body元素和嵌套章节。

过渡元素

文档树元素:transition

过渡元素在小说和短篇小说中经常看到,通常是一条分割线。过渡元素分隔其他body元素。过渡元素不能开始或结束一个章节或文档,也不允许两个过渡元素紧挨在一起。

过渡元素的语法是一个横向的由4个以上的重复标点组成的横线。过渡元素需要在分割线前后都有一个空行:

Para.

 


 

Para.

和标题的下划线不同,过渡元素的标记不会代表任何层级结构,不同的过渡元素标记符也不会有什么区别。我们推荐你在同一篇文档中只使用一种果断元素样式。

Body元素

段落

文档树元素:paragraph。

段落元素就是开头不带任何指示标记的左对齐的文本块。使用空行来区分各个段落。段落内部可能包含内联标记。

语法图:

+——————————+

| paragraph                    |

|                              |

+——————————+

 

+——————————+

| paragraph                    |

|                              |

+——————————+

无序列表

文档树元素:bullet_list, list_item。

无序列表的一项由”*”, “+”, “-“, “•”, “‣”, 或 “⁃” 符号打头,然后跟着空格。列表项的内容必须是左对齐的,并且根据序标正确缩进。序标后面的第一个文字决定的缩进的级别。例如:

  • This is the first bullet list item.  The blank line above the

first list item is required; blank lines between list items

(such as below this paragraph) are optional.

 

  • This is the first paragraph in the second item in the list.

 

This is the second paragraph in the second item in the list.

The blank line above this paragraph is required.  The left edge

of this paragraph lines up with the paragraph above, both

indented relative to the bullet.

 

  • This is a sublist.  The bullet lines up with the left edge of

the text blocks above.  A sublist is a new list so requires a

blank line above and below.

 

  • This is the third item of the main list.

 

This paragraph is not part of the list.

下面是一些不正确的例子:

  • This first line is fine.

A blank line is required between list items and paragraphs.

(Warning)

 

  • The following line appears to be a new sublist, but it is not:

  • This is a paragraph continuation, not a sublist (since there’s

no blank line).  This line is also incorrectly indented.

  • Warnings may be issued by the implementation.

语法图:

+——+———————–+

| “- ” | list item             |

+——| (body elements)+      |

+———————–+

有序列表

文档树元素:enumerated_list,list_item。

有序列表和无序列表类似,只不过使用可数标志来代替序标。下面这些可数序列是能被识别的:

  • 阿拉伯数字:1,2,3,…
  • 大写字母:A,B,C,…,Z
  • 小写字母:a,b,c,…,z
  • 大写罗马数字:I,II,III,IV,…,MMMMCMXCIX(4999)
  • 小写罗马数字:i,ii,iii,iv,…,mmmmcmxcix(4999)

另外,自动编号指示符“#”可以用来表示这个序号是自动编号的。自动枚举可以从显示枚举开始,显示枚举指定了序号的起始下标。全部使用自动序号的话,下标将从1开始。

下面这些格式是可以被识别的:

  • 用句号当后缀: A. a. I. i.
  • 用括号括起来:(1) (A) (a) (I) (i)
  • 用右括号当后缀:1) A) a) I) i)

在解析有序列表的时候,遇到以下情况会重新开始一个新的列表:

  • 遇到了不同格式的有序列表的序标(例如,“”和“(a)”就会产生两个有序列表)
  • 序标不连续(例如,“”和“3.”会产生两个有序列表)

建议将有序列表的第一个列表项设置为序号1。尽管用其它数字作为起始数字是可以被识别的,但是输出格式不一定支持。警告级别为1【info】的系统消息会提示有序列表的下标不为1。

有序列表的列表项的第二行元素会检查有效性。这是为了防止将首字符恰好和有序列表的标识符一致的普通的段落作为列表项内容进行解析。例如,下面的内容将被解析成普通段落。

  1. Einstein was a really

smart dude.

然而,如果这个段落只有一行的话,重复无法避免。下面这段就会被解析成列表项:

  1. Einstein was a really smart dude.

在这种情况下,第一个字符应该被转义来避免这种情况。

\A. Einstein was a really smart dude.

嵌套有序列表的例子:

  1. Item 1 initial text.

 

  1. a) Item 1a.
  2. b) Item 1b.

 

  1. a) Item 2a.
  2. b) Item 2b.

语法图举例:

+——-+———————-+

| “1. ” | list item            |

+——-| (body elements)+     |

+———————-+

定义列表

文档树元素:definition_list,deifinition_list_item,term,classifier,definition。

每一个定义列表都由一个term,可选的classifiers,和一个definition组成。term是一个单行的单词或短语。可选的classifier可以出现在term的同一行的后面,用“ : ”(空格,冒号,空格)隔开。definition是一个关联term的缩进文本块,可以包含多个段落或者其它元素。term和definition之间不能有空行(这是为了和引用块区分开)。在第一个term前面和最后一个definition后面都必须要有空行,但是两个定义项之间可以有空行也可以没有空行。例如:

term 1

Definition 1.

 

term 2

Definition 2, paragraph 1.

 

Definition 2, paragraph 2.

 

term 3 : classifier

Definition 3.

 

term 4 : classifier one : classifier two

Definition 4.

在classifier的分隔符“ : ”前面的term行中内容可以识别内联标记。只有在所有内联标记外面的分隔符“ : ”才可以被正确识别。

定义列表可以使用在很多地方:

  • 作为字典或者词汇表出现。term就是单词,classifier就可以用作描述term的用途(名词,动词等),然后definition就是term的定义。
  • 描述程序的变量。term就是变量的名称,classifier就用来描述变量的类型(字符串,整型等),definition就描述了这个变量在程序中的作用。定义列表的这种用法支持classifier语法:Grouch,也就是一套描述以及执行中的Python对象图表。

语法图:

+—————————-+

| term [ ” : ” classifier ]* |

+–+————————-+–+

| definition                 |

| (body elements)+           |

+—————————-+

字段列表

文档树元素:field_list,field,field_name,field_body。

字段列表是一段扩展语法,就像指定中的选项一样。它也可以用作只有两列的表格一样的结构,类似于数据库记录(标签和数值对)。reStructuredText的程序可能可以识别出字段名称并转换成当前内容的字段或字段体。例如,下面的书目字段,或者“image”和“meta”指令。

字段列表通过字段名字段体来建立映射,这个模型在RFC822中建模。字段名可以包含任意字符,但是内部的冒号必须进行转义。字段名中的内联标记可以正确识别。字段名是大小写不敏感的。字段名采用前缀冒号外加后缀冒号来完成定义。字段名标记后面必须有空格和字段体。字段体可以包含多个body元素,根据字段标记来决定缩进。字段名标记后面的第一行的缩进决定了字段体的缩进。例如:

:Date: 2001-08-16

:Version: 1

:Authors: – Me

  • Myself

  • I

:Indentation: Since the field marker may be quite long, the second

and subsequent lines of the field body do not have to line up

with the first line, but they must be indented relative to the

field name marker, and they must line up with each other.

:Parameter i: integer

该要如何解析字段名中的每一个单词,这将由解析器决定。解析器可以识别字段名中的语法。例如,第二个以及后面的单词可以被认为是“参数”,引号引起来的部分被认为是单个参数,或者可以直接支持“name=value”这样的语法。

这种结构不能直接用RFC822的标准,是因为其中有冲突。在一般的书面语中,一行的开始位置是一个冒号后面跟着一个单词,这种情况非常常见。但是,在某些定义的文本内容下,可以使用标准的RFC822头。

语法图:

+——————–+———————-+

| “:” field name “:” | field body           |

+——-+————+                      |

| (body elements)+                  |

+———————————–+

书目列表

文档树元素:docinfo,author,authors,organization,contact,version,status,date,copyright,field,topic。

当文档中的第一个非注释元素是字段列表(如果有文档标题的话,那就是文档标题后的第一个非注释元素),那么这个字段列表就会被解释成文档的书目列表。书目列表数据一般和书的首页有关,比如标题页和版权页。

某些预定义的字段名(将在下面列出)会被识别并转换成相关的文档树元素,大部分都转换成“docinfo”的子元素。这些字段没有规定顺序,尽管他们在文档树结构中可能会被重新排序。除了下面列出的一些特殊情况外,每一个字段元素的字段体只能包含一个单独的段落。字段体会被RCS关键字检查并清理。任何无法识别的字段都会作为“docinfo”普通字段存在。

下面列出了文档的预定义书目字段名和关联的文档树元素:

  • Author:作者元素
  • Authors:多名作者元素
  • Organization:组织元素
  • Contact:联系方式元素
  • Address:地址元素
  • Version:版本元素
  • Status:状态元素
  • Date:日期元素
  • Copyright:版权元素
  • Dedication:话题元素
  • Abstract:话题元素

“Authors”字段可以包含:一个包含多个作者的单独段落,用分号或者逗号隔开;一个无序列表,每个列表项都是一个写着作者名字的单行段落。分号会比逗号先检查,所以“Doe, Jane; Doe, John”可以正确识别。在某些语言中(比如瑞典语),“Author”和“Authors”之间是没有单复数区别的,所以仅提供“Authors”字段,并且只有一个名字的时候就应该转换成“Author”字段。如果一个单个的名字中包含逗号,需要在单个名字的最后面加个分号来进行区分。

“Address”用来填写多行的邮件地址。里面所有的换行和空格都不会进行转换。

“Dedication”和“Abstract”字段可能包含任意body元素。这两个当中只能出现一个。最终会解析成“Dedication”标题的或“Abstract”标题的topic元素。topic元素直接跟在docinfo元素后面。

上述的字段名到元素的映射可以替换成其它语言。

未定义的一般字段的字段体可以包含多个段落或者body元素。在被转换成一个有效的标识符表后,字段名称会被同坐“classes”属性。

RCS关键字

解析器识别书目字段的时候会使用RCS(Revision Control System,版本控制系统)关键字来进行检查和清理。另外,RCS关键字检查功能可以被关闭,但是现在还没有实现这个功能。RCS关键字会替换掉源文件中的“$keyword$”字段,并且当文件被存储在RCS或者CVS中后,就会被扩展成“$keyword: expansion text$”。例如,一个“Status”字段可以被转换成“status”元素:

:Status: $keyword: expansion text $

在处理完成之后,“status”元素的文本就会简单地变成“expansion text”。美元符号和keyword头都会被移除。

RCS关键字检查智慧在数据列表中的字段内容中进行处理。

选项列表

文档树元素:option_list,option_list_item,option_group,option,option_string,option_argument,description

选项列表是一个两列的列表,包含命令行的参数和描述,用来描述一个程序的参数选项。例如:

-a         Output all.

-b         Output both (this description is

quite long).

-c arg     Output just arg.

–long     Output all day long.

 

-p         This option has two paragraphs in the description.

This is the first.

 

This is the second.  Blank lines may be omitted between

options (as above) or left in (as here and below).

 

–very-long-option  A VMS-style option.  Note the adjustment for

the required two spaces.

 

–an-even-longer-option

The description can also start on the next line.

 

-2, –two  This option has two variants.

 

-f FILE, –file=FILE  These two options are synonyms; both have

arguments.

 

/V         A VMS/DOS-style option.

在reStructuredText中识别以下几种选项的类型:

  • POSIX短选项格式,一个横线加一个字符
  • POSIX长选项格式,两个横线加一个单词;有些系统仅使用一个横线
  • 旧的GNU风格加选项,一个加号加一个字符(现在已经不建议使用这个格式了)
  • DOS/VMS选项,一个正斜线加一个选项单词或字母

需要注意的是,POSIX风格和DOS/VMS风格的选项会同时使用在DOS和Windows的程序上,所以经常这两者会混合使用。上面的命名只是为了方便而已。

POSIX的长格式或短格式选项的语法遵从Pyhthon的getopt.py模块。reStructuredText并不支持所有的选项系统。

尽管一些长的选项在命令行中可以截断,但是在reStructuredText中没有特别的语法来支持这种特性,必须给出完整的选项。

选项后面可能会跟着一个参数占位符,这个占位符的作用会在描述文本中介绍。选项和占位符之间可以同等号或者空格分隔。短选项则会忽略分隔符。选项参数应该使用下面的两种形式中的一种:

  • 字母打头([a-zA-Z]),并且由字母,数字,下划线和连字符组成([a-zA-Z0-9_-])。
  • 用尖括号(<>)括起来,内部允许任何非尖括号字符。

可能需要列出多个同义选项,共享同一个描述,选项之间需要用“逗号空格”分开。

选项和描述之间必须至少要有2个空格。描述体可以包含多个body元素。选项标记后的第一行决定了描述部分的缩进。参数列表之间可以没有空行,但是第一个参数的前面和最后一个描述的后面必须要有空行。

语法图:

+—————————-+————-+

| option [” ” argument] ”  ” | description |

+——-+——————–+             |

| (body elements)+                 |

+———————————-+

文本块

文档树元素:literal_block

一个由“::”(两个冒号)组成的段落表明了后面跟着的文本内容是一个文本块。文本块必须缩进或者引用(见后面)。在文本块内部不会处理任何标记。里面的内容会原样输出,并且通常会用等宽字体输出:

This is a typical paragraph.  An indented literal block follows.

 

::

 

for a in [5,4,3,2,1]:   # this is program code, shown as-is

print a

print “it’s…”

a literal block continues until the indentation ends

 

This text has returned to the indentation of the first paragraph,

is outside of the literal block, and is therefore treated as an

ordinary paragraph.

仅包含两个冒号的段落会被完全移除,不会留下任何空白段落。

为了方便,一个段落末尾的“::”也会被识别。如果双冒号前面有空白字符,那么双冒号会被完全移除。如果文本后面紧跟着双冒号,那么就会留下一个冒号。

换句话说,下面这些例子都是完全等效的:

  1. 扩展形式:

Paragraph:

 

::

 

Literal block

  1. 部分最小形式:

Paragraph: ::

 

Literal block

  1. 完全最小形式:

Paragraph::

 

Literal block

所有的空白字符(包括换行符,但是除去用于缩进块的空白字符)都会被保留。文本块的前后都必须要有一个空行。但是这些空行不算在文本块的内容当中。

缩进文本块

缩进文本块是根据周围文本进行缩进标记的。解析时会把用于缩进的空格给去除。文本块不需要是连续的,允许文本块内部出现空行。最后的同等级缩进结束一个文本块。

语法图:

+——————————+

| paragraph                    |

| (ends with “::”)             |

+——————————+

+—————————+

| indented literal block    |

+—————————+

引用文本块

引用文本块是非缩进的连续文本块,每一行都用同样的非字母数字7位ASCII字符打头。允许以下字符“! ” # $ % & ‘ ( ) * + , – . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~”。用空行来结束一个引用文本块。引用的字符也会输出到处理后的文档中。

下面是一个例子:

John Doe wrote::

 

> Great idea!

>

Why didn’t I think of that?

 

You just did!  😉

语法图:

+——————————+

| paragraph                    |

| (ends with “::”)             |

+——————————+

+——————————+

| “>” per-line-quoted          |

| “>” contiguous literal block |

+——————————+

行块

文档树元素:line_block,line。

行块主要用于地址块,诗歌,歌词以及一些基本的列表。在这些结构中,换行符是需要保留的。行块就是以“|”打头的一堆行的组合。每一个竖线都代表了最终结果的一行,所以最后的换行符会被保留。竖线前面的缩进依然表示这个模块的层级结构。内部是支持内联标记的。注意,当某些行太长而不得不换行的时候,新的一行最前面要用空格来代替竖线的位置。竖线左边必须要有缩进,但是不必和上一个文本块进行对齐。通过一个空行来结束行块。

下面是一个例子:

| Lend us a couple of bob till Thursday.

| I’m absolutely skint.

| But I’m expecting a postal order and I can pay you back

as soon as it comes.

| Love, Ewan.

下面这个例子展示了行块的嵌套。嵌套是由新行的初始缩进来指明的。

Take it away, Eric the Orchestra Leader!

 

| A one, two, a one two three four

|

| Half a bee, philosophically,

|     must, ipso facto, half not be.

| But half the bee has got to be,

|     vis a vis its entity.  D’you see?

|

| But can a bee be said to be

|     or not to be an entire bee,

|         when half the bee is not a bee,

|             due to some ancient injury?

|

| Singing…

语法图:

+——+———————–+

| “| ” | line                  |

+——| continuation line     |

+———————–+

引用块

文档树元素:block_quote,attribution。

如果一块文本相对于前面的文本有缩进,并且没有任何标记指明这块内容是什么块,那么这块内容就是引用块。引用块内部的所有标记都会被正常处理。

This is an ordinary paragraph, introducing a block quote.

 

“It is my business to know things.  That is my trade.”

 

— Sherlock Holmes

引用块的最后可能由属性结束:以“–”或者“—”或者真的破折号打头的文字块。如果属性由多行组成,第二行及以后几行必须对其。

在属性结束一个引用块之后可以连续接着多个引用块。

Unindented paragraph.

 

Block quote 1.

 

—Attribution 1

 

Block quote 2.

可以用空注释来明确指明上一个结构的结束,这样可以避免将一个引用块解释成上一个结构的嵌套引用块。

  • List item.

 

..

 

Block quote 3.

空注释也可以用来分隔两个引用块。

Block quote 4.

 

..

 

Block quote 5.

语法图:

+——————————+

| (current level of            |

| indentation)                 |

+——————————+

+—————————+

| block quote               |

| (body elements)+          |

|                           |

| — attribution text       |

|    (optional)             |

+—————————+

Doctest块

文档树元素:doctest_block

Doctest模块其实就是Python交互会话中的内容复制到docstring中的结果。这种内容通常用来演示用法,并在Python的doctest模块中可以提供优雅和有效的测试环境。

Doctest块是“>>>”打头的文本块,然后是Python的主要输出,然后用空行结束。Doctest块其实是一个特殊的文本块,和文本块的语法一致。当同事出现文本块语法和doctest语法,会优先选择文本块语法。

This is an ordinary paragraph.

 

>> print ‘this is a Doctest block’

this is a Doctest block

 

The following is a literal block::

 

>> This is not recognized as a doctest block by

reStructuredText.  It will be recognized by the doctest

module, though!

Doctest块并不必须要缩进。

表格

文档树元素:table,tgroup,colspec,thead,tbody,row,entry。

reStructuredText提供了两种表格的语法:网格表格和简单表格。

和其它元素类似,表格的前后也都需要有一个空行来隔开。表格的左边距需要和前面一个文本块对齐。如果表格被缩进的话,表格就会被当成引用块处理。

一旦画好表格,每个表格的每个单元格都被当成一个迷你的文档。表格的上下分割线就是分隔文档的空行。每个单元格都可以包含0个或多个body元素。单元格内容里的左右边距会在处理的时候移除。

网格表

网格表就是用ASCII字符完全地画出一张表格。网格表的单元格可以有任意的body元素,行和列的跨越也可以支持。不管怎样,这种表格创建的时候还是非常麻烦的,尤其是单个数据集的时候。

网格表是通过“-”,“=”,“|”和“+”拼成的一个可视化的表格。“-”用来话横向表格线(分隔行),“=”用来分隔表标题和表格体的。“|”用来表示表格的竖线(分隔列)。“+”用来表示表格行和列的交叉点。下面是个例子:

+————————+————+———-+———-+

| Header row, column 1   | Header 2   | Header 3 | Header 4 |

| (header rows optional) |            |          |          |

+========================+============+==========+==========+

| body row 1, column 1   | column 2   | column 3 | column 4 |

+————————+————+———-+———-+

| body row 2             | Cells may span columns.          |

+————————+————+———————+

| body row 3             | Cells may  | – Table cells       |

+————————+ span rows. | – contain           |

| body row 4             |            | – body elements.    |

+————————+————+———————+

绘制表格线的时候,需要小心内容影响表格的表达。比如下面的内容,表格第二行包含了一个跨越3列的单元格。

+————–+———-+———–+———–+

| row 1, col 1 | column 2 | column 3  | column 4  |

+————–+———-+———–+———–+

| row 2        |                                  |

+————–+———-+———–+———–+

| row 3        |          |           |           |

+————–+———-+———–+———–+

如果你的单元格内容中正好有一个竖线字符,就有可能错误的分开单元格。

+————–+———-+———–+———–+

| row 1, col 1 | column 2 | column 3  | column 4  |

+————–+———-+———–+———–+

| row 2        | Use the command ls | more.   |

+————–+———-+———–+———–+

| row 3        |          |           |           |

+————–+———-+———–+———–+

正确的做法是,可以加个空格错开竖线:

+————–+———-+———–+———–+

| row 1, col 1 | column 2 | column 3  | column 4  |

+————–+———-+———–+———–+

| row 2        |  Use the command ls | more.  |

+————–+———-+———–+———–+

| row 3        |          |           |           |

+————–+———-+———–+———–+

或者让这一行跨越两列:

+————–+———-+———–+———–+

| row 1, col 1 | column 2 | column 3  | column 4  |

+————–+———-+———–+———–+

| row 2        | Use the command ls | more.   |

|              |                                  |

+————–+———-+———–+———–+

| row 3        |          |           |           |

+————–+———-+———–+———–+

简单表

简单表提供了输入表格的简单便捷形式,这种表格要求表格的数据集是横向的。单元格的内容一般是单个段落,尽管大部分单元格可以放置任意元素。简单表支持内容多行(除了第一列)以及跨越多列,但是不支持跨越行。

简单表用“-”和“=”构成表格边框线。“=”用来表示表格的上下边界线以及表格的标题边界线。“-”用来表示跨越多列,也可以用于表示分隔一行,不过不是必须的。

简单表的第一行是由等号绘制的边界线,并且用空格来分隔每列的边界(建议用两个或更多的空格)。不考虑合并的话,上边界必须完整描述了表格的所有列。一个简单表格必须要有两列以上(和标题区分)。上边界下面可以跟着表格的标题行,标题行后面要跟着等号画的分割线。它们之间不能有空行,否则标题行分割线会被解释为表格下边界。表格的下边界和上边界类似。下面是个例子:

=====  =====  =======

A      B    A and B

=====  =====  =======

False  False  False

True   False  False

False  True   False

True   True   True

=====  =====  =======

“-”可以用来指示多个邻近的单元格被合并成同一个单元格了。合并的单元格下边界必须完整给出(必须跨越所有合并的单元格)并且和已经建立的单元格边界对齐。跨列分割线仅仅作用于它上面的一行内容。下面是个例子:

=====  =====  ======

Inputs     Output

————  ——

A      B    A or B

=====  =====  ======

False  False  False

True   False  True

False  True   True

True   True   True

=====  =====  ======

每一行文字在列边界的时候必须有空格,除非这一行是跨越这一列的。每一行都代表了一个新的表格行,除非在第一列有一个空白的单元格。在这种情况下,多行文字会被当成连续的单行文字。因此,第一列的新行必须至少包含一些文字。同样,这个机制也要求了第一行的内容必须是单行的。

提示:如果想没有内容也新起一行的话,可以用空注释或转义空白。

简单表里的空行也是允许的,怎样解析需要依赖上下文。两行之间的空行会被忽略,夸多行的文本中的空行可以分割段落。

下面的例子演示了多行,空行。文字的内容超越的表格的右边距。

=====  =====

col 1  col 2

=====  =====

1      Second column of row 1.

2      Second column of row 2.

Second line of paragraph.

3      – Second column of row 3.

 

  • Second item in bullet

list (row 3, column 2).

\      Row 4; column 1 will be empty.

=====  =====

显示标记块

显示标记块有以下特征:

  • 第一行以“..”打头,然后跟着空格;
  • 第二行以及后面几行都有缩进;
  • 最后以没有缩进的行结束。

显示标记块和无序列表类似,以“..”作为序标。序标后面的第一个文本内容的缩进决定了整块内容的缩进。第二行以及后面的子行的最大公共缩进会被移除,所以,如果第一个结构满足第一行,而第二个结构和第一个结构的缩进不一致,第一个结构就不能和显示标记行起始一样开始。

显示标记语法前后需要有空行分隔,现在用于脚注,引用标注,连接目标,指令,子替换定义,以及注释。

脚注

文档树元素:footnote,label

每一个脚注都包含一个显示标记块指示符(“..”),方括号内部是脚注标签,然后是空格,再后面是缩进元素。脚注标签可以是:

  • 一个完整的数字
  • 一个简单的“#”
  • 一个“#”后面隔着一个索引名字
  • 一个简单的“*”

脚注内容也一样要缩进并且左对齐。脚注内容的第一行可以和标签在同一行。如果第一行的缩进和剩下的不同,那么第一行必须和脚注标签同一行。

脚注可以出现在文档的任何位置,不一定要在最下面。最终它们会出现在哪决定于处理程序。

下面是一个手动标号脚注的例子。

.. [1] Body elements go here.

每一个脚注都会自动生成一个到自己的链接。这个超链接的名字和脚注的标签一致。自动序号脚注会生成一个数字并用这个数字当引用名称。

语法图:

+——-+————————-+

| “.. ” | “[” label “]” footnote  |

+——-+                         |

| (body elements)+        |

+————————-+

自动序号脚注

当脚注的标签的第一个字符是“#”的时候,表明这个脚注是自动生成序号并以这个序号索引的。

自动序号数字是从1开始的,然后按照出现的次序依次增加。

脚注可以明确指明一个标签,这样就可以在自动序号的时候重复引用同一个脚注:[#label]。自动序号标签主要完成两件事情:

  • 对于脚注本身,生成一个超链接目标,这个目标的索引名字就是标签(不包括前面的“#”)。
  • 他们允许脚注通过标签来多次引用。例如:

If [#note]_ is the first footnote reference, it will show up as

“[1]”.  We can refer to it again as [#note]_ and again see

“[1]”.  We can also refer to it as note_ (an ordinary internal

hyperlink reference).

 

.. [#note] This is the footnote labeled “note”.

自动序号的顺序决定于脚注出现的顺序,而不是引用的顺序。如果脚注不是用自动序号标签的话,脚注和脚注引用必须有同样的前后顺序,但是不必在意脚注和脚注引用之间的顺序。例如:

[#]_ is a reference to footnote 1, and [#]_ is a reference to

footnote 2.

 

.. [#] This is footnote 1.

.. [#] This is footnote 2.

.. [#] This is footnote 3.

 

[#]_ is a reference to footnote 3.

需要特别小心脚注本身有脚注引用的情况,或者是在一些接近的位置多次引用的情况。脚注和引用按照他们在文档中出现的顺序排序,和用户阅读他们的顺序无关。

自动符号脚注

星号“*”可以用来请求生成自动符号脚注和脚注引用。星号必须是标签中的唯一内容。例如:

Here is a symbolic footnote reference: [*]_.

 

.. [*] This is the footnote.

处理器会将符号作为标签插入到相关的脚注和脚注引用中。引用数目必须的脚注数目一致,一个符号脚注不能有多个引用。

标准的Docutils系统使用以下符号作为脚注标记:

  • 星号“*”
  • dagger (HTML character entity “&dagger;”, Unicode U+02020)
  • double dagger (“&Dagger;”/U+02021)
  • section mark (“&sect;”/U+000A7)
  • pilcrow or paragraph mark (“&para;”/U+000B6)
  • number sign (“#”)
  • spade suit (“&spades;”/U+02660)
  • heart suit (“&hearts;”/U+02665)
  • diamond suit (“&diams;”/U+02666)
  • club suit (“&clubs;”/U+02663)

如果需要的符号超过了10种,可以使用重复序列,例如“**”。

手动序号和自动序号混合

在一篇文档中手动序号和自动序号可能会被混合使用。手动序号有着最高的优先级,只有没有用过的脚注序号才会被用在自动脚注序号上。下面是个例子:

[2]_ will be “2” (manually numbered),

[#]_ will be “3” (anonymous auto-numbered), and

[#label]_ will be “1” (labeled auto-numbered).

 

.. [2] This footnote is labeled manually, so its number is fixed.

 

.. [#label] This autonumber-labeled footnote will be labeled “1”.

It is the first auto-numbered footnote and no other footnote

with label “1” exists.  The order of the footnotes is used to

determine numbering, not the order of the footnote references.

 

.. [#] This footnote will be labeled “3”.  It is the second

auto-numbered footnote, but footnote label “2” is already used.

引用标注

文档树元素:citation。

引用标注就是使用非数字标签的脚注。引用标注的标签是简单引用名称。引用标注在渲染的时候可能会和脚注分开。例如:

Here is a citation reference: [CIT2002]_.

 

.. [CIT2002] This is the citation.  It’s just like a footnote,

except the label is textual.

超链接目标

文档树元素:target

超链接目标也叫做显式链接目标,和隐式链接目标不同。

超链接目标指明了一个文档内部或者外部的位置,这个目标可以被超链接引用连接。

超链接目标可以被命名也可以匿名。命名的超链接目标由显示标记“..”,一个下划线,引用名称(后面不用加下划线),一个冒号,空格,链接地址块组成:

.. _hyperlink-name: link-block

匿名链接目标由显示标记“..”,两个下划线,一个冒号,一个空白字符以及链接块组成:

.. __: anonymous-hyperlink-target-link-block

匿名链接目标也可以用以下的形式(两个下划线,空格,链接地址块):

__ anonymous-hyperlink-target-link-block

有以下三种链接目标:

  1. 内部超链接目标,这种目标的链接地址留空。这种目标提供了文档内部跳转的连接点。一个内部链接目标链接的位置是跟着自己的元素,例如:

Clicking on this internal hyperlink will take us to the target_

below.

 

.. _target:

 

The hyperlink target above points to this paragraph.

内部链接目标是可以相互链接的。多个相邻的链接目标指向的都是同一个元素。

.. _target1:

.. _target2:

 

The targets “target1” and “target2” are synonyms; they both

point to this paragraph.

如果这个元素指向了外部链接目标的话,外部链接目标就会传播到内部链接目标,他们将都指向同样的URI。这就没有必要拷贝重复的URI了。例如,下面三个目标都指向了同样的URI:

.. _Python DOC-SIG mailing list archive:

.. _archive:

.. _Doc-SIG: http://mail.python.org/pipermail/doc-sig/

同时,内联的超链接格式也是支持的。

同样,如果一个内部链接目标是嵌套在一个内部缩进文本块最后,那么这将导致给单独的列表项设置目标(除了第一个,因为第一个列表项前面设置目标会对整个列表项设置目标)。

  • bullet list

 

.. _second item:

 

  • second item, with hyperlink target.

 

  1. 外部超链接目标有一个绝对或相对的URI或者邮件地址。例如:

See the Python_ home page for info.

 

Write to me_ with your questions.

 

.. _Python: http://www.python.org

.. _Write to me: jdoe@example.com

转换成HTML输出之后,就会像下面这样:

See the <a href=”http://www.python.org”>Python</a> home page

for info.

 

<a href=”mailto:jdoe@example.com”>Write to me</a> with your

questions.

外部链接目标的URI应该从显示标记的同一行开始书写,或者是一个紧着的有缩进的文本块,中间不能有空行。如果一个链接文本包含多行,他们是级联的。任何没有转义的空格都将被删除(空白字符允许用来折叠行)。下面的外部链接目标是等效的。

.. _one-liner: http://docutils.sourceforge.net/rst.html

 

.. _starts-on-this-line: http://

docutils.sourceforge.net/rst.html

 

.. _entirely-below:

http://docutils.

sourceforge.net/rst.html

转义空格会保留为内部空格,例如:

.. _reference: ../local\ path\ with\ spaces.html

如果一个URI的最后一个字符是下划线,那么这个下划线必须要转义:

This link_ refers to a file called underscore_.

 

.. _link: underscore_

允许直接使用链接引用来定义URI,但是不建议这么做。

  1. 间接链接目标使用链接引用来作为其链接目标。在下面的例子中,三个目标引用的都是同一个目标:

.. one: two

.. two: three

.. _three:

和在文中其它位置引用链接目标一致,在URI处引用短语目标也需要用反引号引起来。下面例子中的目标都引向同一个位置:

.. one-liner: A HYPERLINK

.. _entirely-below:

a    hyperlink_

.. _split: `A

Hyperlink`_

允许包含一个别名直接引用目标。

如果引用名中包含冒号:

  • 引用名必须用反引号引起来:

.. _FAQTS: Computers: Programming: Languages: Python:

http://python.faqts.com/

  • 或者冒号应该转义:

.. _Chapter One: “Tadpole Days”:

 

It’s not easy being green…

语法图:

+——-+———————-+

| “.. ” | “_” name “:” link    |

+——-+ block                |

|                      |

+———————-+

匿名链接目标

匿名链接目标和自动编号脚注类似,匿名链接目标会降低文档的可读性,需要小心使用。匿名链接通过两个下划线识别:

See the web site of my favorite programming language__.

匿名链接目标以“.. __:”打头,不允许出现引用名称,以及简称,上面以及提及。

引用名称不用来和引用目标匹配。他们是按照出现顺序匹配的:第一个引用和第一个目标匹配。匿名引用和匿名目标的数量必须一样。为了可读性,最好将目标放在引用附近。

指令

文档树元素:依赖于具体指令

指令是reStructuredText的扩展机制,是一种不修改语法就能添加新结构的方法。所有内建的指令都写在另一个文件中,并且一直有效。

例如,下面演示了怎样引入图片:

.. image:: mylogo.jpeg

图标(图片加标题)例子如下:

.. figure:: larch.png

 

The larch.

带其他body内容的警告框:

.. note:: This is a paragraph

 

  • Here is a bullet list.

指令以“.. ”打头,后面跟着指令类型,然后两个冒号,一个空格(合在一起称为指令标记)。指令类型应该是一个大小写不敏感的单词(字母数字,横线,下划线,加号,冒号,分号,不能有空格)。指令后面用两个冒号有以下原因:

  • 常规文本中很少出现连着的两个冒号
  • 两个冒号可以防止将注释解析为指令
  • 如果某个指令没有被支持,会产生一个level3(error)的系统消息,整个指令块会被当成文本块解析,“::”是很自然的选择

指令包含以下三个部分:

  1. 指令参数
  2. 指令选项
  3. 指令内容

一个单独的指令可以是上面三项内容的任意组合。指令参数可以是文件路径,URL,标题文本等。选项使用字段列表指出。字段名字和内容都是直接指定的。参数和选项必须连续在一起(意味着,不能有空行),用空行来表示内容部分的开始。下面是同时具有这三部分的figure指令:

.. figure:: larch.png

:scale: 50

 

The larch.

简单的指令可能不包含任何内容部分。如果一个指令不需要内容部分,但是后面却要跟着缩进内容的话,会导致错误。可以用空注释来避免这种情况。

指令可以任意处理它们的内容模块,甚至可能把内容模块变得和原来完全不一样。内容模块也可以给出编译指示,修改编译器行为,甚至修改语法。然而,现在并不支持这样,直到有一天有充分理由需要这么干的时候,就会支持的。

指令不会生成指令元素,它是一种编译器结构,对于reStructuredText内部没有意义。编译器会将解析到的指令转换成可识别的文档元素。

语法图:

+——-+——————————-+

| “.. ” | directive type “::” directive |

+——-+ block                         |

|                               |

+——————————-+

替换定义

文档树元素:substitution_definition

替换定义由“.. ”打头,然后跟着竖线,然后是替换标签,然后是竖线,空格,然后是定义块。替换定义标签的前后不能有空白字符。替换定义块中包含一个内嵌的指令(没有前导的“.. ”),比如“image”或者“replace”。例如:

The |biohazard| symbol must be used on containers used to

dispose of medical waste.

 

.. |biohazard| image:: biohazard.png

定义块中直接或者间接包含循环定义引用的话会发生错误。

替换定义引用的地方会进行相应的替换,替换定义是大小写敏感的。但是需要注意的是,如果没有找到合适的替换,会用大小写不敏感的比较再尝试一次。

替换可以让块级指令很方便地用在行内文本上。这是一种很方便的将复杂结构引入文本内部的方式。这个有点像编程语言中的宏替换。

语法图:

+——-+—————————————————–+

| “.. ” | “|” substitution text “| ” directive type “::” data |

+——-+ directive block                                     |

|                                                     |

+—————————————————–+

下面是一些替换的常见的用法,需要注意的是,大部分例子中的兼容指令仅仅是例子而已,还没有具体实现。

对象替换

替换引用可以将一个含糊不清的文本和一个唯一的对象标识符链接在一起。

例如,许多站点可能希望实现一个内部的“user”指令:

|Michael| and |Jon| are our widget-wranglers.

 

.. |Michael| user:: mjones

.. |Jon|     user:: jhl

根据网站的需求,这个替换可能用在后面的文档搜索,然后将内联文本进行链接(mailto,主页,悬停显示联系方式,等),或者修改文本的显示方式(引入用户名字,或者图标等)。

同样的方法也可以用在频繁引用某个固定类型对象但是拥有模糊名字的标签上。例如:

|The Transparent Society| offers a fascinating alternate view

on privacy issues.

 

.. |The Transparent Society| book:: isbn=0738201448

类或者函数,在当前文本中指向不明,或者解析文本无法发现,可以这样用:

4XSLT has the convenience method |runString|, so you don’t

have to mess with DOM objects if all you want is the

transformed output.

 

.. |runString| function:: module=xml.xslt class=Processor

图像

图像替换是替换的常见用法:

West led the |H| 3, covered by dummy’s |H| Q, East’s |H| K,

and trumped in hand with the |S| 2.

 

.. |H| image:: /images/heart.png

:height: 11

:width: 11

.. |S| image:: /images/spade.png

:height: 11

:width: 11

 

  • |Red light| means stop.

  • |Green light| means go.

  • |Yellow light| means go really fast.

 

.. |Red light|    image:: red_light.png

.. |Green light|  image:: green_light.png

.. |Yellow light| image:: yellow_light.png

 

|-><-| is the official symbol of POEE_.

 

.. |-><-| image:: discord.png

.. _POEE: http://www.poee.org/

图像指令是已经实现了的。

样式

可以替换内联文本为某个指定的外部样式:

Even |the text in Texas| is big.

 

.. |the text in Texas| style:: big

模板

内联文本可能用来为以后的模板引擎处理服务。例如:

Welcome back, |name|!

 

.. |name| tal:: replace user/getUserName

替换文本

可以直接进行重复用到多次的文本的替换:

|RST|_ is a little annoying to type over and over, especially

when writing about |RST| itself, and spelling out the

bicapitalized word |RST| every time isn’t really necessary for

|RST| source readability.

 

.. |RST| replace:: reStructuredText

.. _RST: http://docutils.sourceforge.net/rst.html

注意第一个替换后面的下划线,这指明了此处要进行一个超链接替换。

替换也可以用在一些没法内联的文本上,或者一些特别长的文本上:

But still, that’s nothing compared to a name like

|j2ee-cas|__.

 

.. |j2ee-cas| replace::

the Java TM:super: 2 Platform, Enterprise Edition Client

Access Services

__ http://developer.java.sun.com/developer/earlyAccess/

j2eecas/

替换指令已经实现了。

注释

文档树结构:comment

在“.. ”后面的缩进文本会被处理成文档注释。注释文本中不会有任何处理。根据输出格式,注释可能会被完全删除。注释的唯一约束就是,不会使用其它已经精确定义的结构:替换,指令,脚注,引用,或者链接,这些全都会被忽略。为了防止和其它结构冲突,将“..”单独放一行就可以了。

.. This is a comment

..

_so: is this!

..

[and] this!

..

this:: too!

..

|even| this:: !

“..”后面跟空行,不带任何文本,就是空注释。经常用空注释结束文本结构。但是,空注释不会抵消缩进。想要在引用块后面跟一个列表或者其它缩进结构,插入一个没有缩进的空注释。

语法图:

+——-+———————-+

| “.. ” | comment              |

+——-+ block                |

|                      |

+———————-+

隐式链接目标

章节标题,脚注,引用标注,以及扩展结构中都有可能生成隐式链接目标。其他情况下,隐式链接目标和显式链接目标表现一致。

为了解决隐式引用名和显示引用名的歧义问题,可以采取以下步骤:

  1. 显式链接目标会覆盖任何同名的隐式链接目标。这种情况下,隐式链接目标会被删除,并产生一个level1的信息。
  2. 重复的隐式链接目标会被移除,同时level1的信息会报告。例如,多个章节标题拥有同样的名字,他们就会拥有同样的链接目标。
  3. 重复的显示目标会被移除,并产生level2的警告。有种情况例外,扩展链接目标(通过链接名字和URI来唯一标识)不会冲突,并且不会被移除。

内联标签

内联标记作用于单词或者短语。用于分隔单词的空格和标点也同样可以分隔内联标签。内联标签内部的文字不能以空格打头或者结束。某些字符级别的内联标签也是支持的,尽管并不鼓励使用。内联标签是不能嵌套的。

一共有9种内联标签结构。下面这5种的开始字符串和结束字符串是相同的:

  • 强调:“*”
  • 突出强调:“**”
  • 解析文本:“`”
  • 行内文本块:““”
  • 替换引用:“|”

下面这3种的开始字符串和结束字符串是不一样的:

  • 内联内部目标:“_`”和“`”
  • 脚注引用:“[”和“]_”
  • 超链接引用:“`”和“`_”,单词可以直接加“_”进行引用

独立链接是隐式识别的,不会用任何特殊的标记。

内联标签识别法则

内联标签的开始标记和结束标记只会在以下条件中进行识别:

  1. 内联标签的开始标签后面必须不能有空白字符。
  2. 内联标签的结束标签后面必须不能有空白字符。
  3. 内联标签的起始标签和结束标签之间必须至少又要一个字符。
  4. 内联标签的开始标记和结束标记前面不能存在未转义的反斜杠(除了内联文本块以外)。
  5. 如果一个内联标签的开始标记前面跟着一个以下字符(’ ” < ( [ {或者类似的unicode字符),则开始标记后面不能跟着对应的关闭符号(’ ” ) ] } >或者类似的unicode字符)。(对于引号,对应字符可以是所有的引用标记)

如果配置设置中的simple-inline-markup为False(默认),将会有以下附加条件:

  1. 内联标记的开始标记必须是开始一个文本块,或者紧跟在以下字符后面:
    • 空白字符
    • 以下字符:- : / ‘ ” < ( [ {
    • 或者一个类似的unicode标点字符
  2. 内联标签的结束标记必须结束一个文本块或者跟着以下内容:
    • 空白字符
    • 以下字符之一:- . , : ; ! ? \ / ‘ ” ) ] } >
    • 或者一个类似的unicode标点

上面的这些规则可以允许90%以上的“”,“”,“_”,“|”的非标签用法。例如,下面这些写法都不会识别成内联标签:
<ul><li>2 * x a ** b (* BOM32_*
“ _ __ | (breaks 1)

  • || (breaks 3)
  • ” ‘|’ () [] {} <> ‘’ ‚‘ ‘‚ ’’ ‚’ “” „“ “„ ”” „” »« ›‹ «» »» ›› (breaks 5)
  • 2x ab O(N2) e**(xy) f(x)f(y) a|b file. __init__ __init__() (breaks 6)
  • 下面这些内联标签例子都不需要进行转义:

    • 2 * x *a **b *.txt (breaks 2)
    • 2*x a**b O(N**2) e**(x*y) f(x)*f(y) a*(1+2) (breaks 7)

    虽然,上面的这些建议使用内联文本,尤其是代码片段的时候。

    下面这些例子需要使用文本引用或者转义来避免错误解析:

    *4, class_, *args, **kwargs, `TeX-quoted’, *ML, *.txt

    在大多数情况下,内联文本块是最佳选择(默认情况下,这一步会选择等宽字体)。

    对于某些不使用空格来分割文字的(比如日语和中文),通常会要求设置simple-inline-markup为True。

    识别顺序

    一些分割标签符号会同时用作多个结构,所以必须定义识别顺序来防止歧义。内联标签的识别顺序如下:

    • 星号:先识别突出强调“**”,然后才是强调“*”
    • 反引号:内联文本(“)和内联目标(“_`”和“`”)之间不会冲突,并且会先于链接目标(“`”和“`_”)和解析文本(`)识别。
    • 尾部下划线:脚注和链接可以互相区分。
    • 竖线:不会有冲突。
    • 独立链接目标是最后识别的。

    字符级别内联标签

    可以允许对单词内部的某些字符进行内联,可以通过转义实现。转义可以实现将某些本来不能跟在内联标签后面的文本直接跟在内联标记后面:

    Python list\s use square bracket syntax.

    强调

    文档树元素:emphasis

    起始标记 = 结束标记 = “*”

    强调通常显示为斜体。

    突出强调

    文档树元素:strong

    起始标记 = 结束标记 = “**”

    突出强调通常显示为粗体。

    可解析文本

    文档树结构:和处理过程的角色有关

    开始标记 = 结束标记 = “`”

    可解析文本标记文本为有关的,可以索引的,链接的,可总结的,或者其它处理但是单词本身是独立的。

    This is interpreted text.

    角色指明了可解析文本的解析方式。角色可以不指明(如上写法,会使用默认角色),或者明确指明角色。用role标记指明角色。role标记由冒号,角色名称,冒号组成。角色名称是一个引用标签。role标签可以作为可解析文本的前缀或者后缀:

    :role:interpreted text

     

    interpreted text:role:

    可解析文本允许对内联表述标记进行扩展。我们可以对强调,突出强调,内联文本和超链接,加上标题引用,索引实体,类,红色,闪烁等任何我们想要的效果。只有预先定义的角色才能被识别。不识别的角色会报错。

    内联文本

    文档树结构:literal

    开始标记 = 结束标记 = ““”

    内联文本中可以包含任何字符,除了两个反引号。内部不会有任何标记解析。

    内联文本内部的换行符不会保留。尽管解析器会保留所有空格字符,但是输出格式不一定会保留。如果空白字符很重要,请使用文本块。

    超链接引用

    文档树元素:reference

    • 命名链接引用:
      • 无开始标记,结尾标记“_”
      • 开始标记 = “`”,结束标记 = “`_”
    • 匿名引用:
      • 无开始标记,结尾标记“__”
      • 开始标记 = “`”,结束标记 = “`__”

    嵌入式URI和别名

    链接引用可以通过“<>”直接嵌入目标URI或者链接引用:

    See the Python home page &lt;http://www.python.org&gt;_ for info.

     

    This link &lt;Python home page_&gt;_ is an alias to the link above.

    尖括号前面必须要有空格,必须是链接标记的最后部分。

    如果使用单下划线,这个引用就是命名引用,并且同样的URI可以再次被引用到。使用两个下划线的话,引用和目标就都是匿名的,目标也不会被再次引用。这就是“一次性”链接,例如:

    RFC 2396 &lt;http://www.rfc-editor.org/rfc/rfc2396.txt&gt;__ and `RFC

    2732 <http://www.rfc-editor.org/rfc/rfc2732.txt>`__ together

    define the syntax of URIs.

    独立链接作为URI处理,尽管是下划线结尾:

    __init__ &lt;http:example.py.html#__init__&gt;__

    如果一个链接因为下划线最末尾而导致识别失败的话,需要转义下划线:

    Use the source &lt;parrots.txt\_&gt;__.

    引用文字也可以忽略,这种情况下,URI会被当成引用文字。

    See &lt;a_named_relative_link&gt;_ or &lt;an_anonymous_relative_link&gt;__

    for details.

    内联内部目标

    文档树元素:target

    开始标记 = “_”,结束标记 = “

    内联内部目标等价于显式内部链接目标,但是可以在文本内部出现。内联内部目标不能是匿名的。

    例如,下面的文本中包含一个链接目标“Norwegian Blue”:

    Oh yes, the _Norwegian Blue.  What’s, um, what’s wrong with it?

    脚注引用

    文档树元素:footnote_reference

    配置设置:footnote_referrences,trim_footnote_reference_space

    开始标记 = “[”,结束标记 = “]_”

    footnote_referrences可以设置脚注上标,trim_footnote_reference_space可以移除脚注前面的空格。

    引用

    文档树元素:citation_reference

    和脚注引用类似。

    替换引用

    文档树元素:substitution_reference,reference

    起始标记 = 结束标记 = “|”,后面可选的跟“_”或“__”

    独立链接

    文档树元素:reference

    文字内部的URI或者邮件地址会作为通用外部链接存在。例如:

    See http://www.python.org for info.

    最终输出结果为:

    See <a href=”http://www.python.org”>http://www.python.org</a> for

    info.

    以下两种URI会被识别:

    1. 绝对URI。
    2. 单独的邮件地址。

    跟在URI后面的标点符号会不被当做URI的一部分,如果需要包括可以用尖括号,或者转义。“*”和“_”都是有效的URI字符。

    单位

    reStructuredText解析器支持以下单位:

    • em
    • ex
    • px
    • in
    • cm
    • mm
    • pt
    • pc

    这些和CSS中的长度单位关联。

    没有指定单位的数值采用什么单位依赖于编辑器。

    也可以支持百分号作为单位。

    错误处理

    在PEP 258中定义。

     

     

     

     

     

     

     

     

    分类
    dpdk analysis

    dpdk 内存 管理浅析—-内存申请和释放

    1.dpdk 内存分配 dpdk在分配内存时都会调用rte_malloc_socket 函数接口分配内存。该函数 申明如下

    void *rte_malloc_socket(const char *type, size_t size, unsigned align, int socket_arg)
    

    参数

    type 用来保存内存分配的名字

    size 分配的内存大小

    align 分配的内存对齐

    socket_arg 在那个socket上分配内存

    rte_malloc_socket 函数主要有三个功能

    a.获取分配内存属于哪个socket 代码如下:

      if (!rte_eal_has_hugepages())
              socket_arg = SOCKET_ID_ANY; //如果没有大页则根据运行线程的cpu所在的socket分配内存
    
      if (socket_arg == SOCKET_ID_ANY)
              socket = malloc_get_numa_socket();// 获取运行线程的cpu所在socket
      else
              socket = socket_arg; //用户指定了socket。按照用户指定socket分配
    

    b.根据获取的socket,调用malloc_heap_alloc分配内存

      ret = malloc_heap_alloc(&mcfg->malloc_heaps[socket], type,
                                size, 0, align == 0 ? 1 : align, 0);
    

    c. 如果用户没有指定socket,且分配失败,从其他socket上分配内存

      if (ret != NULL || socket_arg != SOCKET_ID_ANY)//如果用户制定了socket 则直接失败
                return ret;
    
        /* try other heaps */
        for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
                /* we already tried this one */
                if (i == socket)
                        continue;
    
                ret = malloc_heap_alloc(&mcfg->malloc_heaps[i], type,
                                        size, 0, align == 0 ? 1 : align, 0); //从其他socket上分配内存
                if (ret != NULL)
                        return ret;
        }
    

    内存堆管理函数

    void* malloc_heap_alloc(struct malloc_heap *heap,
                const char *type __attribute__((unused)), size_t size, unsigned flags,
                size_t align, size_t bound)
    

    参数基本与rte_malloc_socket函数一致。(注意 type 阐述其实没有任何卵用)。关键代码如下:

    elem = find_suitable_element(heap, size, flags, align, bound);//在堆中找到合适的内存块(大小大于要求大小)
    elem = malloc_elem_alloc(elem, size, align, bound);//根据需求将内存块列分成需要的大小,并将剩余块保存
    

    find_suitable_element 函数,dpdk在管理内存时将内存块按照内存块大小的对数作为索引,插入到队列链表中,当有内存分配需求是,根据需要的大小的对数查找空闲内存。代码如下:

    for (idx = malloc_elem_free_list_index(size);           //计算所需内存的大小的对数,以此为索引,检索内存
                        idx < RTE_HEAP_NUM_FREELISTS; idx++) {
                for (elem = LIST_FIRST(&heap->free_head[idx]);
                                !!elem; elem = LIST_NEXT(elem, free_list)) {
                        if (malloc_elem_can_hold(elem, size, align, bound)) { //检查内存块是否满足要求,内存块大小大于所需大小
                                if (check_hugepage_sz(flags, elem->ms->hugepage_sz))
                                        return elem;
                                if (alt_elem == NULL)
                                        alt_elem = elem;
                        }
                }
        }
    
        if ((alt_elem != NULL) && (flags & RTE_MEMZONE_SIZE_HINT_ONLY))
                return alt_elem;   //如果找到合适的内存块则返回,否者返回空 ,分配失败
    

    malloc_elem_alloc 函数,这个函数根据需求大小和对齐要求将内存进行切分。代码如下

    struct malloc_elem *new_elem = elem_start_pt(elem, size, align, bound); //计算新内存块的元数据位置
    const size_t old_elem_size = (uintptr_t)new_elem - (uintptr_t)elem; //计算分配剩下的内存块大小,
    const size_t trailer_size = elem->size - old_elem_size - size -    //计算对齐造成的尾部数据块大小
            MALLOC_ELEM_OVERHEAD;
    
    elem_free_list_remove(elem);   //将内存块元数据从空闲内存库链表中剥离下来
    
    if (trailer_size > MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) { //如果尾部对齐造成空隙够大,则创建一个新的空闲内存块
                /* split it, too much free space after elem */
                struct malloc_elem *new_free_elem =
                                RTE_PTR_ADD(new_elem, size + MALLOC_ELEM_OVERHEAD);
    
                split_elem(elem, new_free_elem);
                malloc_elem_free_list_insert(new_free_elem);
    }
    
    if (old_elem_size < MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) { //判断截取以后的内存是否足够大,如果不足够大则当做分配内存块的pad存在。返回新分配的内存块
                /* don't split it, pad the element instead */
                elem->state = ELEM_BUSY;
                elem->pad = old_elem_size;
    
                /* put a dummy header in padding, to point to real element header */
                if (elem->pad > 0){ /* pad will be at least 64-bytes, as everything
                                     * is cache-line aligned */
                        new_elem->pad = elem->pad;
                        new_elem->state = ELEM_PAD;
                        new_elem->size = elem->size - elem->pad;
                        set_header(new_elem);
                }
    
                return new_elem;
    }
    
        /* we are going to split the element in two. The original element
         * remains free, and the new element is the one allocated.
         * Re-insert original element, in case its new size makes it
         * belong on a different list.
         */
        split_elem(elem, new_elem); //将内存块列分成两个内存块,根据需要直接列分。 这里跟伙伴系统有些许差异,伙伴系统要求,列分的内存块必须等大。这里列分时,按照需要大小列分。例如一共有100个内存,分配10个内存,当前方法是直接列分成90 -- 10.而不是按照伙伴系统的 先列分成50 -50 在列分成25 -25 再列分成12 -13 。然后再分配。
        new_elem->state = ELEM_BUSY;  //分配的内存设置为 忙碌
        malloc_elem_free_list_insert(elem);  //将列分以后的内存块插入到空闲列表中
    
        return new_elem; //返回分配的内存块
    

    malloc_elem_free_list_insert 函数,代码如下:

    idx = malloc_elem_free_list_index(elem->size - MALLOC_ELEM_HEADER_LEN);//计算对数
    elem->state = ELEM_FREE;  //设置状态为空闲
    LIST_INSERT_HEAD(&elem->heap->free_head[idx], elem, free_list); //插入对应的列表中
    

    2.dpdk内存释放 释放是调用的函数接口为rte_free,该函数直接调用malloc_elem_free函数,代码如下:

    if (!malloc_elem_cookies_ok(elem) || elem->state != ELEM_BUSY)  判断内存是否被越界写,或者是否两次释放
                return -1;
    
        rte_spinlock_lock(&(elem->heap->lock));
        size_t sz = elem->size - sizeof(*elem);
        uint8_t *ptr = (uint8_t *)&elem[1];
        struct malloc_elem *next = RTE_PTR_ADD(elem, elem->size); //获取内存块的下一个相邻内存块元数据
        if (next->state == ELEM_FREE){   //如果下一个内存块也空闲,那么合并当前内存块和下一个内存块。
                /* remove from free list, join to this one */
                elem_free_list_remove(next);
                join_elem(elem, next);
                sz += sizeof(*elem);
        }
    
        /* check if previous element is free, if so join with it and return,
         * need to re-insert in free list, as that element's size is changing
         */
        if (elem->prev != NULL && elem->prev->state == ELEM_FREE) { //判断上一个内存块是否空闲,如果上一个内存块也空闲,将上一个内存块和当前内存块合并。
                elem_free_list_remove(elem->prev);
                join_elem(elem->prev, elem);
                sz += sizeof(*elem);
                ptr -= sizeof(*elem);
                elem = elem->prev;
        }
        malloc_elem_free_list_insert(elem); //将合并以后的内存块插入到空闲列表中共下次使用。
    

    3.总结 dpdk的内存管理方式还是比较简单。主要体现在两个地方的改进。

    第一,使用大页作为内存的载体。减少TLB刷新。

    第二,改进了伙伴系统,将内存可以随意切分。而不是必须想伙伴系统一样,必须二分。这样改进,的确可以加快内存分配速度。(当系统只有大内存块时,需要分配小内存块,伙伴系统需要递归切分。而当前方式不需要递归)。但是会增加外部碎片的概率。可能dpdk是为快速处理数据包而设计的。不会大量保持内存,因此,外部碎片对程序影响不大。

    第三,内存分配区分 socket。根据socket所在位置来分配内存,减少夸socket 内存访问。

    分类
    dpdk analysis

    dpdk 内存 管理浅析—-内存管理初始化

    内存相关命令行参数解析。 在dpdk程序中,最开始都需要调用rte_eal_init 函数用来初始化dpdk环境。在rte_eal_init中 关于初始化内存的管理的代码如下

    eal_log_level_parse(argc, argv); //初始化 internal_config
    fctret = eal_parse_args(argc, argv); // 解析命令行参数
    if (internal_config.no_hugetlbfs == 0 &&
                    internal_config.process_type != RTE_PROC_SECONDARY &&
                    internal_config.xen_dom0_support == 0 &&
                    eal_hugepage_info_init() < 0)//获取当前设备大页信息
            rte_panic("Cannot get hugepage information\n");
    if (internal_config.memory == 0 && internal_config.force_sockets == 0) {
            if (internal_config.no_hugetlbfs)
                    internal_config.memory = MEMSIZE_IF_NO_HUGE_PAGE;
    }
    eal_hugedirs_unlock();
    if (rte_eal_memory_init() < 0)//初始化大页信息
         rte_panic("Cannot init memory\n");
    eal_hugedirs_unlock();
    if (rte_eal_memzone_init() < 0)//初始化内存管理
        rte_panic("Cannot init memzone\n");
    

    1.初始化 internal_config 结构体 在 eal_log_level_parse 函数中调用eal_reset_internal_config 函数初始化代码如下:

    void eal_reset_internal_config(struct internal_config *internal_cfg)
    {
        int i;
        internal_cfg->memory=0;                                   //内存大小
        internal_cfg->force_nrank = 0;                            //内存 rank数量
        internal_cfg->force_nchannel = 0;                         //内存 channel 数量
        internal_cfg->hugefile_prefix = HUGEFILE_PREFIX_DEFAULT;
        internal_cfg->hugepage_dir = NULL;                       //大页目录
        internal_cfg->force_sockets = 0;                         //numa node 数量
        /* zero out the NUMA config */
        for (i = 0; i < RTE_MAX_NUMA_NODES; i++)
                internal_cfg->socket_mem[i] = 0;
        /* zero out hugedir descriptors */
        for (i = 0; i < MAX_HUGEPAGE_SIZES; i++)
                internal_cfg->hugepage_info[i].lock_descriptor = -1;
        internal_cfg->base_virtaddr = 0;
        internal_cfg->syslog_facility = LOG_DAEMON;
        /* default value from build option */
    #if RTE_LOG_LEVEL >= RTE_LOG_DEBUG
        internal_cfg->log_level = RTE_LOG_INFO;
    #else
        internal_cfg->log_level = RTE_LOG_LEVEL;
    #endif
    
        internal_cfg->xen_dom0_support = 0;
        /* if set to NONE, interrupt mode is determined automatically */
        internal_cfg->vfio_intr_mode = RTE_INTR_MODE_NONE;
    #ifdef RTE_LIBEAL_USE_HPET
        internal_cfg->no_hpet = 0;
    #else
        internal_cfg->no_hpet = 1;
    #endif
        internal_cfg->vmware_tsc_map = 0;
        internal_cfg->create_uio_dev = 0;
    }
    
    1. 解析命令行参数填充internal_config 结构体

    解析分为两部分,第一部分是通过调用eal_parse_common_option完成关键代码如下:

    case 'm':                                   //总共使用多少M内存,
        conf->memory = atoi(optarg);
        conf->memory *= 1024ULL;
        conf->memory *= 1024ULL;
        mem_parsed = 1;
        break;
    /* force number of channels */
    case 'n':                                   //channel数量
        conf->force_nchannel = atoi(optarg);
        if (conf->force_nchannel == 0) {
               RTE_LOG(ERR, EAL, "invalid channel number\n");
               return -1;
        }
        break;
    /* force number of ranks */
    case 'r':                                //rank 数量
        conf->force_nrank = atoi(optarg);
        if (conf->force_nrank == 0 ||
            conf->force_nrank > 16) {
               RTE_LOG(ERR, EAL, "invalid rank number\n");
               return -1;
        }
        break;
    case OPT_NO_HUGE_NUM:                   //是否开启大页
        conf->no_hugetlbfs = 1;
        break;
    
    第二部分在internal_config中直接解析 关键代码
    
    case OPT_HUGE_DIR_NUM:                         //获取大页的挂在目录
            internal_config.hugepage_dir = optarg;
            break;
    
    case OPT_FILE_PREFIX_NUM:                     //设置大页文件的前缀
            internal_config.hugefile_prefix = optarg;
            break;
    
    case OPT_SOCKET_MEM_NUM:                      //设置每个num node 上内存的数量
            if (eal_parse_socket_mem(optarg) < 0) {
                  RTE_LOG(ERR, EAL, "invalid parameters for --"
                                OPT_SOCKET_MEM "\n");
                          eal_usage(prgname);
                          ret = -1;
                          goto out;
            }
            break;
    
    1. 获取设备当前大页信息 eal_hugepage_info_init

    a.通过打开/sys/kernel/mm/hugepages目录。根据子目录的目录数量得出当前系统上有几种类型的大页。

    b.通过目录名字获取每种类型的大页页面大小。 c.通过大小和/proc/mount文件确定每种类型的大页挂在在系统的那个位置。(没有挂载的大页类型不能用,如果用户指定了大页目录,只能使用指定的大页目录)

    d.通过/sys/kernel/mm/hugepages/目录下每个子目录中空闲大页和保留大页数量 计算出 可用大页数量。并将可用数据全部记录在numa node 0上,后续会区分。

    e.将所有信息记录在internal_config.hugepage_info数组中,并根据页面大小由大到小排序。 关键代码如下:

    dir = opendir(sys_dir_path);
    ****
    hpi = &internal_config.hugepage_info[num_sizes];
    hpi->hugepage_sz =
           rte_str_to_size(&dirent->d_name[dirent_start_len]);
    hpi->hugedir = get_hugepage_dir(hpi->hugepage_sz);
    ****
    hpi->num_pages[0] = get_num_hugepages(dirent->d_name);
    ****
    qsort(&internal_config.hugepage_info[0], num_sizes,
              sizeof(internal_config.hugepage_info[0]), compare_hpi);
    

    4.初始化大页信息 rte_eal_memory_init 初始化大页信息时会判断当前进程是不是主进程,如果是则执行rte_eal_hugepage_init 进行大页初始化 a.没有大页情况,直接调用mmap 分配内存 代码如下

    if (internal_config.no_hugetlbfs) {
             addr = mmap(NULL, internal_config.memory, PROT_READ | PROT_WRITE,
                             MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
             if (addr == MAP_FAILED) {
                    RTE_LOG(ERR, EAL, "%s: mmap() failed: %s\n", __func__,
                            strerror(errno));
                    return -1;
             }
             mcfg->memseg[0].phys_addr = (phys_addr_t)(uintptr_t)addr;
             mcfg->memseg[0].addr = addr;
             mcfg->memseg[0].hugepage_sz = RTE_PGSIZE_4K;
             mcfg->memseg[0].len = internal_config.memory;
             mcfg->memseg[0].socket_id = 0;
             return 0;
    }
    

    b.在有大页情况下。会根据前面获取 的大页信息,初始化大页

    1).在挂在大页目录下创建mmap映射文件。并获取 映射后的虚拟地址。如果全部创建成功 说明内存足够,如部分创建失败,则说明内存不够。缩小大小

     pages_new = map_all_hugepages(&tmp_hp[hp_offset], hpi, 1);
    

    2).通过虚拟内存地址获取物理内存地址读取/proc/self/pagemap文件可以获取。

     find_physaddrs(&tmp_hp[hp_offset], hpi)
    

    3).通过读取/proc/self/numa_maps文件获取每一个大页所属node。

    find_numasocket(&tmp_hp[hp_offset], hpi)
    

    4).按照物理地址由小到大排序。

    qsort(&tmp_hp[hp_offset], hpi->num_pages[0],
                      sizeof(struct hugepage_file), cmp_physaddr);
    

    5).将物理地址连续的大页虚拟地址也连续映射。

    map_all_hugepages(&tmp_hp[hp_offset], hpi, 0)
    

    6).解除第一次映射的虚拟地址

    unmap_all_hugepages_orig(&tmp_hp[hp_offset], hpi)
    

    7).统计每个socket每种类型的大页数量

    for (i = 0; i < nr_hugefiles; i++) {
         int socket = tmp_hp[i].socket_id;
    
         /* find a hugepage info with right size and increment num_pages */
         const int nb_hpsizes = RTE_MIN(MAX_HUGEPAGE_SIZES,
               (int)internal_config.num_hugepage_sizes);
         for (j = 0; j < nb_hpsizes; j++) {
               if (tmp_hp[i].size ==internal_config.hugepage_info[j].hugepage_sz) {
                    internal_config.hugepage_info[j].num_pages[socket]++;
                }
        }
    }
    

    8).将以上收集的数据存储到共享内存中,以便其他程序使用。根据收集数据将连续的物理内存视作一个内存块,初始化 mem_config内存管理数据结构的memseg数组,

    for (i = 0; i < nr_hugefiles; i++) {
          new_memseg = 0;
    
          /* if this is a new section, create a new memseg */
          if (i == 0)
                 new_memseg = 1;
          else if (hugepage[i].socket_id != hugepage[i-1].socket_id)
                 new_memseg = 1;
          else if (hugepage[i].size != hugepage[i-1].size)
                 new_memseg = 1;
    
    #ifdef RTE_ARCH_PPC_64
          /* On PPC64 architecture, the mmap always start from higher
          * virtual address to lower address. Here, both the physical
          * address and virtual address are in descending order */
          else if ((hugepage[i-1].physaddr - hugepage[i].physaddr) !=
              hugepage[i].size)
                 new_memseg = 1;
          else if (((unsigned long)hugepage[i-1].final_va -
              (unsigned long)hugepage[i].final_va) != hugepage[i].size)
                  new_memseg = 1;
    #else
          else if ((hugepage[i].physaddr - hugepage[i-1].physaddr) !=
              hugepage[i].size)
                  new_memseg = 1;
          else if (((unsigned long)hugepage[i].final_va -
              (unsigned long)hugepage[i-1].final_va) != hugepage[i].size)
                  new_memseg = 1;
    #endif
    
          if (new_memseg) {
                 j += 1;
                 if (j == RTE_MAX_MEMSEG)
                         break;
    
                 mcfg->memseg[j].phys_addr = hugepage[i].physaddr;
                 mcfg->memseg[j].addr = hugepage[i].final_va;
                 mcfg->memseg[j].len = hugepage[i].size;
                 mcfg->memseg[j].socket_id = hugepage[i].socket_id;
                 mcfg->memseg[j].hugepage_sz = hugepage[i].size;
          }
          /* continuation of previous memseg */
          else {
    #ifdef RTE_ARCH_PPC_64
          /* Use the phy and virt address of the last page as segment
           * address for IBM Power architecture */
                  mcfg->memseg[j].phys_addr = hugepage[i].physaddr;
                  mcfg->memseg[j].addr = hugepage[i].final_va;
    #endif
                  mcfg->memseg[j].len += mcfg->memseg[j].hugepage_sz;
          }
          hugepage[i].memseg_id = j;
    }
    

    5.内存管理初始化 rte_eal_memzone_init 该函数最后调用rte_eal_malloc_heap_init对内存进行初始化

    rte_eal_malloc_heap_init函数根据memseg 属于哪个numa node 调用malloc_heap_add_memseg初始化memsege内存元素 并加入对应的numa node 堆中

    malloc_heap_add_memseg初始化代码如下:

    static void
    malloc_heap_add_memseg(struct malloc_heap *heap, struct rte_memseg *ms)
    {
        /* allocate the memory block headers, one at end, one at start */
        struct malloc_elem *start_elem = (struct malloc_elem *)ms->add //memseg中第一个元素
        struct malloc_elem *end_elem = RTE_PTR_ADD(ms->addr,   //memseg中最后一个元素
                        ms->len - MALLOC_ELEM_OVERHEAD);
        end_elem = RTE_PTR_ALIGN_FLOOR(end_elem, RTE_CACHE_LINE_SIZE);
        const size_t elem_size = (uintptr_t)end_elem - (uintptr_t)start_elem; //memseg中第一个元素大小
    
        malloc_elem_init(start_elem, heap, ms, elem_size); //初始化memseg中第一个元素大小,并与堆关联,初始化时会根据编译选项做debuge校验。
        malloc_elem_mkend(end_elem, start_elem);
        malloc_elem_free_list_insert(start_elem);          //根据元素大小,将元素插入到堆中空闲队列组中,空闲队列组的下表表示该队列中存放的空闲元素的大小大于2的下表次幂。
        heap->total_size += elem_size;          //堆的总大小增加
    }
    

    6.总结 dpdk的内存初始化,大致到此。下一节将讲述,dpdk如何分配和释放内存

    分类
    未分类

    分享 c 中重定向输入输出流到文件的简单方法

    对于经常去刷 ACM 以及其他各种算法题的同学们来说,有时候需要许多的测试用例来验证是否会被 AC,但每次手动输入大量测试用例、调试又是极为麻烦的,本文分享一个比较简单的将标准输入输出流重定向到文件中的方法,是借助 freopen 函数。

    freopen 原型函数声明于 stdio.h文件中,原型为:

    FILE* freopen(char const* _FileName,char const* _Mode,FILE* _Stream);

    其中,filename 是需要重定向到的文件名,mode 为文件访问权限字符串,比如 “w” 为写权限, “r” 为读权限,stream 为需要被重定向的文件流。先上一下简单的示例代码:

    int main()
    {
        int x, y;
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
        scanf_s("%d,%d", &x, &y);
        printf("%d,%d\n", x, y);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

    freopen(“in.txt”, “r”, stdin) 就是将 stdin(标准输入流)重定向到工作目录下 in.txt 文件中,之后的 scanf 操作实际上是从 事先准备好的 in.txt 中读取的。 同时将重定向标准输出流 stdout 到 out.txt 文件中 。

    假如 in.txt 文件内容为 30,20 ,运行完代码后,控制台不会有输出,30,20\n 将会写入 out.txt 文件中。这样,就可以将较长的测试用例输入一次性写在文件里,方便调试程序了。

    在大多数比较流行的的 c 编译器版本中,freopen 的使用会有警告导致编译无法通过,在 Visual Studio 中,下列解决方法:

    1. 使用 #pragma warning(disable:4996) disable 掉
    2. 在项目 –> 属性 –> C++ –> 预处理器 的预处理器定义中添加 _CRT_SECURE_NO_WARNINGS
    3. 使用 freopen_s 函数

    freopen_s 是 freopen 的安全版本,原型为:

    errno_t freopen_s(FILE** _Stream, char const* _FileName, char const* _Mode, FILE* _OldStream);

    相比而言,比 freopen 多了一个输入参数,返回值为是否重定向成功的标志变量。

    int main()
    {
        int x, y;
        FILE* inFile, *outFile;
        freopen_s(&inFile, "in.txt", "r", stdin);
        freopen_s(&outFile, "out.txt", "w", stdout);
        scanf_s("%d,%d", &x, &y);
        printf("%d,%d\n", x, y);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    
    分类
    未分类

    c++ 中 malloc 与 new 的几点区别

    在 c++ 中,malloc 与 new 都是分配内存可以用的方法,它们在使用中也存在不少区别,整理了下,主要可分为以下几个方面:


    malloc 与 free 是 c 的库函数,而 new 和 delete 是 c++ 中的运算符。在 c++ 中,new 与 delete 是可以被重载的。 自定义重载 new 和 delete 主要是为了防止内存泄漏问题。


    malloc 返回的是 void* 指针,因此调用 malloc 申请指定类型的内存对象时,需要进行对应的强制类型转换。而 new 返回的即为调用它的类型的指针。比如:

    int* ptr1 = (int*)(malloc(sizeof(int)));
    int* ptr2 = new int;
    free(ptr1);
    delete ptr2;
    

    单对象申请内存时, malloc 需要计算出分配空间大小, 而 new 不需要。为数组分配空间时,两者的调用方式也不尽相同,比如分配一个长度为10的整型数组空间:

    int* ptrArray1 = (int*)(malloc(sizeof(int) * 10);
    int* ptrArray2 = new int[10];
    free(ptrArray1);
    delete[] ptrArray2;
    

    在面向对象中, malloc 执行并不会调用类的构造函数,而 new 会调用类构造函数。上一段示例代码:

    class Test
    {
    public:
        int x = 3;
        void print()
        {
            cout << hex << x << endl;
        }
    }
    int main()
    {
        Test* pTest1 = (Test*)malloc(sizeof(Test));
        pTest1 -> print();
        Test* pTest2 = new Test;
        pTest2 -> print();
        free(pTest1);
        delete pTest2;
        return 0;
    }
    

    运行结果是

    cdcdcdcd
    3
    

    顺便提一下 Visual Studio 以及 VC 中对未初始化的堆内存以及栈内存的处理。在 Debug 模式下,编译器会将未初始化的堆内存填充 0xCD ,而对未初始化的栈内存填充 0xCC 。简体中文 Windows 下使用的是 GBK 编码,一连串的 0xCDCDCDCD 就会被解析为“屯屯屯屯”,而一连串的 0xCCCCCCCC 会被解析为“烫烫烫烫”,这就是不小心使用了未被初始化的堆栈内存导致的。在上面的代码中,由于 malloc 不会调用类的构造函数,为它分配的堆内存单元被填充了 0XCD,会有上面的输出结果。而 new 运算符会调用默认构造函数(上面代码是成员变量默认初始化,本质上一样)。下面上一段测试未被分配的栈内存的代码:

    class Test
    {
    public:
        int x;
        void print()
        {
            cout << hex << x << endl;
        }
    }
    int main()
    {
        Test test = new Test;
        test.print();
        return 0;
    }
    

    运行结果是

    cccccccc
    

    如果想用 malloc 并调用对象的构造函数,有如下两种方案。 1. 显示调用构造函数 2. 使用 placement new

    示例代码如下:

    class Test
    {
    public:
        int x = 3;
        void print()
        {
            cout << hex << x << endl;
        }
    }
    int main()
    {
        Test* pTest1 = (Test*)malloc(sizeof(Test));
        pTest1 -> print();
        Test* pTest2 = (Test*)malloc(sizeof(Test));
        pTest2 -> Test::Test();
        pTest2 -> print();
        Test* pTest3 = (Test*)malloc(sizeof(Test));
        new(pTest3)Test();
        pTest3 -> print();
        free(pTest1);
        free(pTest2);
        pTest3 -> ~Test();
        free(pTest3);
        return 0;
    }
    

    运行结果是

    cdcdcdcd
    3
    3
    

    第一种方式比较容易理解,placement new 实际上完成了 new 调用构造函数的过程,它是在已经开辟好的内存空间上调用类的构造函数。placement new 不应该使用delete,因为它是完成了调用构造函数的过程,应该调用对象的析构函数。


    分配内存失败后的返回值不同。

    malloc 分配内存失败后会返回 NULL,而使用 new 时分配空间失败会抛出 bad_alloc 异常,需要使用 try catch 捕获 。不过,可以借助全局 new 的另外一个重载形式:

    void* operator new(std::size_t, std::nothrow_t const &) throw();

    来解决此问题。 重载形式的示例代码如下

    int* ptr = new(std::nothrow) int;
    if(ptr == NULL)
    {
        //todo
    }
    

    这样,就可以像 malloc 一样,通过 NULL 来判断内存是否分配成功了。


    是否可以直接扩充内存。

    在分配内存空间后,如果在使用过程中发现内存空间不足需要扩充,malloc 分配的空间可以使用 realloc 直接扩容,而 new 的不可以。

    int main()
    {
        int* array = (int*)(malloc(sizeof(int) * 3));
        cout << hex << array[3] << endl;
        array = (int*)(realloc(array, sizeof(int) * 5));
        cout << hex << array[3] << endl;
        free(array);
        return 0;
    }
    

    运行结果是:

    fdfdfdfd
    cdcdcdcd
    

    上述代码中,malloc 首先分配了三个连续的整型内存空间,此时输出 array[3] 是未定义的行为,而 realloc 将内存空间直接扩充为五个连续整型,此时输出结果是未初始化的堆内存,也就是0xCD。

    以上就是部分 malloc 与 new的不同之处,在 oop 中,还是推荐 new 比较多一些,但 malloc 也有自己的部分优势。

    分类
    Visual Studio

    如何创建 .vsct 文件

    如何创建 .vsct 文件

    下面列出了创建 .vsct 文件的几个方法。

    • 你可以通过 VS 扩展包模板创建一个新的扩展包工程来创建。
    • 你可以通过 vsct 编译器,vsct.exe,来通过一个 .ctc 文件来生成 .vsct 文件。
    • 你可以通过 vsct.exe 从一个 .cto 文件来生成 .vsct 文件。
    • 你可以手动创建一个 .vsct 文件。

    手动创建一个 .vsct 文件的步骤

    1. 启动 Visual Studio。
    2. 点击文件菜单,选择新建->文件
    3. 模板面板,选择XML 文件,单击打开
    4. 视图菜单,点击属性窗口来显示 XML 文件的属性。
    5. 属性窗口,点击结构描述属性旁边的浏览(…)按钮。
    6. 在 XSD 结构描述列表中,选择 vsct.xsd 结构描述。如果列表中不存在这个描述,单击添加,然后在本地磁盘中找到这个文件,并单击确定
    7. 在 XML 文件中,输入 <CommandTable 并按下Tab,输入>关闭标签。这样就创建了一个最基本的 .vsct 文件。
    8. 根据 vsct 的结构规则来编写 vsct 文件。

    编译代码

    只是简单的往工程里添加 vsct 文件是不能让其参与编译的,你必须还得配置以下内容。

    添加 vsct 编译的步骤

    1. 在编辑器中打开你的工程文件,如果你的工程文件已经打开,你必须先卸载工程。
    2. 添加一个包含 VSCTCompile 元素的 ItemGroup 元素,如下所示。
      <ItemGroup>
          <VSCTCompile Include="TopLevelMenu.vsct">
              <ResourceName>Menus.ctmenu</ResourceName>
          </VSCTCompile>
      </ItemGroup>
      

      ResourceName元素一般都设置为 Menus.ctmenu

    3. 如果你的工程还包含一个 .resx 文件,需要添加一个包含 MergeWithCTO 元素的 EmbeddedResource 元素,如下所示。

      <EmbeddedResource Include="VSPackage.resx">
          <MergeWithCTO>true</MergeWithCTO>
          <ManifestResourceName>VSPackage</ManifestResourceName>
      </EmbeddedResource>
      

      这个标记需要放置在包含 embedded resources 的 ItemGroup 元素内部。

    4. 在编辑器中打开包文件,通常叫做 ProjectNamePackage.cs 或者 ProjectNamePackage.vb。

    5. 向包的类中添加一个 ProvideMenuResource 属性,如下所示。

      [ProvideMenuResource("Menus.ctmenu", 1)]
      

      其中,第一个参数必须和你定义在工程文件中的 ResourceName 属性一致。

    分类
    Visual Studio

    VS 命令表文件(.vsct)

    VS 命令表文件(.vsct)

    命令表配置文件是一个描述 VSPackage 包含的命令的文件。VS 命令表编译器(VSCTC)会将用XML语法编写的配置文件(.vsct文件)编译成二进制的命令表输出文件(.cto文件)。cto文件和使用CTC(命令表编译器)编译ctc文件的结果一样。但是,VSCT文件有着许多优势,比如XML编辑器。

    VSCT文件描述了VSPackage中的命令的布局和外观。命令包括按钮,组合框,菜单,工具栏以及命令组。

    vsct文件组织了命令(commands),菜单(menus),以及命令组(command groups)。

    vsct文件元素

    CommandTable 元素

    CommandTable 是 vsct 文件的根元素,用来指明这个文件是用来描述命令的实际布局。

    语法

    <CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <Extern>...</Extern>
        <Include>...</Include>
        <Define>...</Define>
        <Commands>...</Commands>
        <CommandPlacements>...</CommandPlacements>
        <VisibilityConstaints>...</VisibilityConstaints>
        <KeyBindings>...</KeyBindings>
        <UsedCommands>...</UsedCommands>
        <Symbols>...</Symbols>
    </CommandTable>
    

    xmlns : 必须有,标准的 XML 命名空间
    language : 可选,用来指定 元素的默认语言。默认是英文。

    VSPackages 是怎样添加用户接口元素的?

    VSPackage 可以添加用户自定义的接口元素,比如菜单,工具栏以及工具窗口。对于 Visual Studio 而言,是通过 .vsct 文件实现的。

    VS 命令表架构

    正如前面所说,命令表支持前面提到的架构原则。命令表的抽象类,数据结构,以及工具结构原理如下:

    • 一共有三种基本元素:菜单,命令,组。菜单可以作为菜单,子菜单,工具栏以及工具窗口出现。命令是指 IDE 中用户可以执行的代码,并且可以作为菜单项,按钮,列表框或其他控件的形式出现。组是菜单以及命令的容器。
    • 每一个元素都由一个定义式指定,这个定义式包括描述了该元素的特征,其相关于其他元素的优先级,以及修改其行为的标志。
    • 每个元素都有一个定位元素来描述这个元素的父元素是谁。一个元素可以有许多父元素,所以一个元素可以出现在图形界面的多个位置。

    每一个命令都必须有一个组作为父元素,即使这个组当中只有唯一一个命令。每一个标准菜单也必须拥有一个组作为父元素。工具栏和工具窗口则作为其他元素的父元素出现。组可以将 Visual Studio 的主菜单,或其他菜单,工具栏或工具窗口作为父元素。

    元素是如何定义的

    .vsct 文件是用 XML 语言写的。.vsct 文件是用来定义所有图形化元素以及定义这些元素出现的位置。包中的所有菜单,组,命令首先需要有一个 GUID 和 ID 的 符号 节。自始至终,菜单,命令和组都必须通过 GUID/ID 的组合方式定义。下面的案例展示了一个 Visual Studio Package 自动生成的 Symbols 段。

    <Symbols>
        <!-- 这是包的 guid。 -->
        <GuidSymbol name="guidMenuTextPkg" value="{b1253bc6-d266-402b-89e7-5e3d3b22c746}" />
    
        <!-- 这个 guid 是用来将菜单和命令组合在一起 -->
        <GuidSymbol name="guidMenuTextCmdSet" value="{a633d4e4-6c65-4436-a138-1abeba7c9a69}">
            <IDSymbol name="MyMenuGroup" value="0x1020" />
            <IDSymbol name="cmdidMyCommand" value="0x0100" />
        </GuidSymbol>
    
        <GuidSymbol name="guidImages" value="{53323d9a-972d-4671-bb5b-9e418480922f}" >
            <IDSymbol name="bmpPic1" value="1" />
            <IDSymbol name="bmpPic2" value="2" />
            <IDSymbol name="bmpPicSearch" value="3" />
            <IDSymbol name="bmpPicX" value="4" />
            <IDSymbol name="bmpPicArrows" value="5" />
        </GuidSymbol>
    </Symbols>
    

    <Symbols> 标签里面的顶级标签是 <GuidSymbol> 。这个元素将名称和 GUIDs 进行映射,这个名字是 IDE 用来唯一定位包中的元素。

    GUIDs 会在 Visual Studio 包模板中自动生成。你也同样可以通过 工具->生成 GUID 菜单来生成唯一的 GUID。

    第一个GuidSymbol元素,“guid[包名称]Pkg”,是这个包本身的 GUID。这个 GUID 是 Visual Studio 用来加载自己用的。通常,这个元素没有子元素。

    按照惯例,菜单和命令都是组合在第二个GuidSymbol元素,”guid[包名称]CmdSet”,然后所有的位图都在第三个GuidSymbol,”guidImages”。你不必非得遵从这种惯例,但是所有的菜单,组,命令,位图都必须是GuidSymbol元素的子元素。

    在第二个GuidSymbol元素代表了这个包中的命令集,也就是一些IDSymbol元素。每一个IDSymbol元素都将一个名字和一个数字映射在一起,并代表了一个命令集中的一个菜单,组或命令。第三个GuidSymbolIDSymbol元素代表了命令中要用到图标。由于一个程序中的 GUID/ID 对必须是唯一的,一个GuidSymbol元素内不允许拥有相同的值。

    菜单,组,命令

    当一个菜单,组,命令拥有 GUID 和 ID,这个元素就可以被添加到 IDE 当中。每一个 UI 元素都必须遵从以下事项:

    • 必须有一个定义在GuidSymbol元素中的 guid 属性。
    • 必须有一个IDSymbol元素中定义 id 属性。guidid 属性共同组成了 UI 元素的签名
    • 必须拥有一个 priority 元素来定义这个元素在其父菜单或组当中的位置。
    • 必须拥有一个带有 guidid 属性的父菜单或组。

    菜单

    所有的菜单都以菜单元素定义在Menus标签当中。菜单必须指定guid,id,priority和一个Parent元素。同时,可以指定以下附加元素和子标签:

    • type属性用来指定菜单作为菜单还是工具栏的形式出现。
    • <Strings>元素中的<ButtonText>元素指定了 IDE 中菜单显示的标题,<CommandName>元素指定了命令窗口中访问这个菜单的名字。
    • 可选标志。可以有一个命令标志元素来改变其在 IDE 中的外观和行为。

    所有菜单元素都必须有一个组元素来作为其父元素,除非这个元素是一个可停靠元素,比如工具栏。可停靠的菜单自己是自己的父元素。

    下面的例子定义了一个出现在 Visual Studio 菜单栏工具菜单旁边的菜单。

    <Menu guid="guidTopLevelMenuCmdSet" id="TopLevelMenu" priority="0x700" type="Menu">
        <Parent guid="guidSHLMainMenu" id="IDG_VS_MM_TOOLSADDINS" />
        <Strings>
            <ButtonText>TestMenu</ButtonText>
            <CommandName>TestMenu</CommandName>
        </Strings>
    </Menu>
    

    组定义在 .vsct 文件中的 Groups 元素中。组就是一个容器。除了作为菜单的分割线,组不会出现在IDE中。定义一个组元素只需要指定签名,优先级以及父元素。

    一个组可以用菜单,另外一个组,或者自己本身作为父元素。但是一般情况下,父元素通常指定为菜单或者工具栏。上面那个例子中定义的菜单就是IDG_VS_MM_TOOLSADDINS组的子菜单,并且那个组是 Visual Studio 菜单栏的一个子元素。下面例子中的组就是前面例子中菜单子菜单。

    <Group guid="guidTopLevelMenuCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidTopLevelMenuCmdSet" id="TopLevelMenu" />
    </Group>
    

    由于这是菜单的一部分,所以这个组一般包含许多命令。但是,它也可以包含其它菜单。子菜单就是这么定义的,如下案例所示。

    <Menu guid="guidTopLevelMenuCmdSet" id="SubMenu" priority="0x0100" type="Menu">
        <Parent guid="guidTopLevelMenuCmdSet" id="MyMenuGroup" />
        <Strings>
            <ButtonText>Sub Menu</ButtonText>
            <CommandName>Sub Menu</CommandName>
        </Strings>
    </Menu>
    

    命令

    IDE 中的命令是定义成 Button 或者 Combo 元素的。如果命令要出现在菜单或者工具栏中,命令必须要以一个组来作为其父元素。

    按钮

    按钮都是定义在Buttons元素下面的。所有的菜单项,按钮,或者其他通过用户单击来执行的命令都被当做按钮。一些按钮还可以包含命令列表。按钮和菜单有着同样的属性设置,并且可以有一个Icon元素来指定按钮的图标。

    下面的例子接着前面的例子定义了一个 button,并且会作为父元素组的父菜单的一个菜单项出现。

    <Button guid="guidTopLevelMenuCmdSet" id="cmdidTestCommand" priority="0x0100" type="Button">
        <Parent guid="guidTopLevelMenuCmdSet" id="MyMenuGroup" />
        <Icon guid="guidImages" id="bmpPic1" />
        <Strings>
            <CommandName>cmdidTestCommand</CommandName>
            <ButtonText>Test Command</ButtonText>
        </Strings>
    </Button>
    

    组合框

    组合框定义在Combos章节当中。每一个Combo元素代表了一个带下拉列表的框。这个组合框会根据type属性来决定是否允许用户编辑。组合框和按钮有着同样的属性和行为,并且可以设置以下附加属性:

    • defaultWidth属性用来指定像素单位的宽度。
    • idCommandList属性用来指定在下拉列表中显示的命令列表。命令列表必须声明在和组合框元素同样的GuidSymbol节点下。

    下面的例子定义了一个组合框。

    <Combos>
        <Combo guid="guidFirstToolWinCmdSet" id="cmdidWindowsMediaFilename"
        priority="0x0100" type="DynamicCombo" idCommandList="cmdidWindowsMediaFilenameGetList" defaultWidth="130">
            <Parent guid="guidFirstToolWnCmdSet" id="ToolBarGroupID"/>
            <CommandFlag>IconAndText</CommandFlag>
            <CommandFlag>CommandWellOnly</CommandFlag>
            <CommandFlag>StretchHorizontally</CommandFlag>
            <Strings>
                <CommandName>Filename</CommandName>
                <ButtonText>Enter a Filename</ButtonText>
            </Strings>
        </Combo>
    </Combos>
    

    位图

    当一个命令指定了一个通过GUID/ID选定一个位图的Icon元素,这个命令就会和图标显示在一起。每一个位图都是通过在Bitmaps下面定义Bitmap元素引入的。位图的定义只需要定义其指向源文件的guidhref属性即可。如果源文件是一个资源文件,则必须同时再定义usedList属性,用来列出资源文件中的可用图像。

    父元素

    下面的表格中说明了一个元素可以将哪些元素定义为父元素。

    元素 定义位置 父元素 子元素
    Groups Menu, Group, 自身 Menus, Groups, Commands
    菜单 Menus 1~n 个 Group 0~n 个 Group
    工具栏 Menus 自身 0~n 个 Group
    菜单项 Buttons 1~n 个 Group,自身 0~n 个 Group
    按钮 Buttons 1~n 个 Group,自身
    组合框 Combos 1~n 个 Group,自身

    菜单,命令和组的定位

    菜单,命令和组可能出现在 IDE 中的多个位置。如果一个元素出现在多个位置,则必须对应的在CommandPlacements元素中添加一个CommandPlacement元素。但是,工具栏不能添加这个元素,因为工具栏不能出现在多个上下文相关的位置中。

    命令定位元素需要指定guididpriority属性。同一个元素的签名必须相同。priority决定了元素之间的位置。当IDE遇到了两个同优先级的元素时,它们的优先级是不可预知的。

    如果一个菜单和组出现在多个地方,则其子元素也会出现在其对应的位置。

    命令可见性和上下文

    当多个 VS 插件安装完之后,会有许许多多的菜单,菜单项,工具栏,这些东西会让IDE变得乱七八糟。为了避免这个问题,你可以通过可见性约束命令标志控制个人UI元素的可见性。

    可见性约束

    可见性约束是通过VisibilityConstraints元素中的VisibilityItem元素指定的。可见性约束指定了UI元素在某个特定的上下文当中是否可见。只有定义在这里面的上下文激活的时候元素才是可见的。如果某个元素并没有在这里面定义,那么这个元素默认就是一直可见的。可见性约束不能应用于组。

    VisibilityItem元素必须制定三个属性:guidid用来指定目标UI元素,context属性用来制定可见的上下文,它使用UI上下文作为值。UI上下文是VSConstants类的成员。每一个VisibilityItem只能指定唯一一个上下文。案例如下:

    <VisibilityConstraints>
        <VisibilityItem guid="guidSolutionToolbarCmdSet" id="cmdidTestCmd" context="UICONTEXT_SolutionHasSingleProject" />
        <VisibilityItem guid="guidSolutionToolbarCmdSet" id="cmdidTestCmd" context="UICONTEXT_SolutionHasMultipleProjects" />
    </VisibilityConstraints>
    

    命令标志

    下面的这些命令标志会影响到菜单和命令的可见性。

    AlwaysCreate
        即使一个菜单没有任何的组或按钮,这个菜单也被创建。
        仅对菜单有效。
    
    CommandWellOnly
        这个标志用在一个命令不出现在顶层菜单但是你又希望这个命令对某些自定义的脚本有效,比如,绑定了一个快捷键。在你的扩展包安装之后,用户可以通过打开选项对话框,然后再在键盘环境选项卡中自定义这些命令的位置。
        对按钮,组合框有效。
    
    DefaultDisabled
        在这种情况下,如果一个命令没有被实现或者 QueryStatus 方法没有被调用,这个命令就被设置为不可用状态。
        对按钮,组合框有效。
    
    DefaultInvisible
        在这种情况下 ,如果一个命令没有被实现或者 QueryStatus 方法没有被调用,这个命令就会被设置为不可见状态。这个属性应该和 DynamicVisibility 标志一起用。
        对按钮,组合框,菜单有效。
    
    DynamicVisibility
        命令的元素可以通过 QueryStatus 方法或者 VisibilityConstraints 中定义的上下文GUID来改变可见性。
        这项设置不能应用于工具栏。顶级工具栏项目可以无效化,但是不能被隐藏。
        应用于菜单时,这个标志同样指定了该菜单的子菜单也会被自动隐藏。这个标志通常被应用于子菜单,因为顶层菜单已经拥有了这样的行为。
        这个标志应该和 DefaultInvisiable 一起使用。
        对按钮,组合框,菜单有效。
    
    NoShowOnMenuController
        如果一个命令在菜单控制器上设置了这个标志,则这个命令不会出现在下拉列表中。
        对按钮有效。
    
    常规需求

    在一个命令被显示和可用之前,你应该通过下面的这些测试:

    • 这个命令被放在了正确的位置。
    • DefaultInvisible标志没有设置。
    • 父菜单或父工具栏是可见的。
    • 由于VisibilityConstraints设置的场景下,命令是可见的。
    • VS包的代码中,实现了IOLECommandTarget接口的代码能够使命令可见并有效。没有接口代码拦截并修改它。
    分类
    未分类

    多字节和宽字符之间的转换方法

    在Windows中,宽字符字符串使用的是UTF16编码,而多字节字符串使用的是GB2312编码,两者无法进行直接赋值。所以,在某些情况下需要对它们进行转换。本人大致用过以下几种转换方法。

    • 使用Windows提供的WideCharToMultiByte系列函数进行转换

    使用WideCharToMultiByte可以将宽字符转换成多字节,而使用MultiByteToWideChar可以将多字节转换成宽字符。使用这两个函数需要包含Windows.h这个头文件。代码例子如下:

    • 宽字符转换成多字节
    int _tmain(int argc, _TCHAR* argv[])
    {
        WCHAR wideChar[] = L"我是一个忧伤的字符串。";
        // 1. 首先指定接收转换结果的指针
        char * multiByte;
        // 2. 然后第一次调用函数,获取存储转换结果所需缓冲区的大小
        int len = WideCharToMultiByte(CP_ACP, NULL,
            wideChar,   // 要被转换的宽字符字符串
            -1,         // 要转换的长度,设为-1表示转换整串
            NULL,       // 第一次调用,接收缓冲区设置为NULL
            NULL,       // 接收缓冲区的大小,设为NULL表示让函数返回需要的大小
            NULL, NULL);
        // 3. 根据返回的结果创建合适大小的缓冲区
        multiByte = new char[len];
        // 4. 第二次调用函数,进行真正的转换
        WideCharToMultiByte(CP_ACP, NULL,
            wideChar,   // 要被转换的宽字符字符串
            -1,         // 要转换的长度,设为-1表示转换整串
            multiByte,  // 第二次调用,设为接收转换结果的缓冲区指针
            len,        // 设定接收缓冲区大小
            NULL, NULL);
        // 5. 转换结束,可以输出查看转换结果
        cout << multiByte << endl;
        delete multiByte;
    
        system("pause");
        return 0;
    }
    
    • 多字节转换成宽字符:
    int _tmain(int argc, _TCHAR* argv[])
    {
        setlocale(LC_ALL, "chs");
        char multiByte[] = "我是一个忧伤的字符串。";
        // 1. 首先指定接收转换结果的指针
        WCHAR * wideChar;
        // 2. 然后第一次调用函数,获取存储转换结果所需缓冲区的大小
        int len = MultiByteToWideChar(CP_ACP, NULL,
            multiByte,      // 要被转换的多字节字符串
            -1,             // 要转换的长度,设为-1表示转换整串
            NULL,           // 第一次调用,接收缓冲区设为NULL
            0);             // 接收缓冲区长度,设为0代表函数返回需要的长度
        // 3. 根据返回的结果创建合适大小的缓冲区
        wideChar = new WCHAR[len];
        // 4. 第二次调用函数,进行真正的转换
        MultiByteToWideChar(CP_ACP, NULL,
            multiByte,      // 要被转换的多字节字符串
            -1,             // 要转换的长度,设为-1表示转换整串
            wideChar,       // 第二次调用,设为接收转换结果的缓冲区
            len);           // 设置接收缓冲区的大小
        // 5. 转换结束,可以输出查看转换结果
        wcout << wideChar << endl;
        delete wideChar;
    
        system("pause");
        return 0;
    }

     

    • 使用wprintf系列函数进行转换

    使用wprintfA函数可以很简单就将宽字符转换成多字节。代码例子如下:

    int _tmain(int argc, _TCHAR* argv[])
    {
        WCHAR * wideChar = L"我是一个忧伤的字符串。";
        char multiByte[MAX_PATH];
        // 一个函数搞定
        // 第一个参数是接收转换的结果
        // 第二个参数设为 "%S" (注意S大写)
        // 第三个参数是要转换的内容
        wsprintfA(multiByte, "%S", wideChar);
        cout << multiByte << endl;
    
        system("pause");
        return 0;
    }

    使用wprintfW函数可以很简单就将多字节转换成宽字符。代码例子如下:

    int _tmain(int argc, _TCHAR* argv[])
    {
        setlocale(LC_ALL, "chs");
        char * multiByte = "我是一个忧伤的字符串。";
        WCHAR wideChar[MAX_PATH];
        // 一个函数搞定
        // 第一个参数是接收转换的结果
        // 第二个参数设为 "%S" (注意S大写)
        // 第三个参数是要转换的内容
        wsprintfW(wideChar, L"%S", multiByte);
        wcout << wideChar << endl;
    
        system("pause");
        return 0;
    }

     

    • 使用wcstombs系列函数进行转换

    wcstombs可以实现从宽字符到多字节的转换,但是编译器会报警建议使用安全的wcstombs_s函数替代(此类报警可以通过#pragma warning(disable:4996)进行关闭)。代码例子如下:

    int _tmain(int argc, _TCHAR* argv[])
    {
        setlocale(LC_ALL, "chs");
        WCHAR * wideChar = L"我是一个很忧伤的字符串。";
        char multiByte[MAX_PATH];
        size_t numConverted = 0;
        // 第一个参数为返回成功转换的字节数,如果不需要可以设为NULL
        // 第二个参数为接收转换结果的缓冲区
        // 第三个参数为要转换的内容
        // 第四个参数为要转换的长度,单位是字节数
        wcstombs_s(&numConverted, multiByte, wideChar, -1);
        cout << "成功转换" << numConverted << "个字节:" << multiByte << endl;
    
        system("pause");
        return 0;
    }

    同样,相反过程的函数就是mbstowcs,对应的安全版本的函数为mbstowcs_s,代码例子如下:

    int _tmain(int argc, _TCHAR* argv[])
    {
        setlocale(LC_ALL, "chs");
        char * multiByte = "我是一个很忧伤的字符串。";
        wchar_t wideChar[MAX_PATH];
        size_t numConverted = 0;
        // 第一个参数为返回成功转换的字节数,如果不需要可以设为NULL
        // 第二个参数为接收转换结果的缓冲区
        // 第三个参数为要转换的内容
        // 第四个参数为要转换的长度,单位是WCHAR长度的个数
        mbstowcs_s(&numConverted, wideChar, multiByte, -1);
        wcout << L"成功转换" << numConverted << L"个字节:" << wideChar << endl;
    
        system("pause");
        return 0;
    }

     

    • 使用ATL的W2A和A2W宏进行转换

    ATL的W2A和A2W宏用起来是最方便的,需要包含头文件atlconv.h。代码例子如下:

    int _tmain(int argc, _TCHAR* argv[])
    {
        setlocale(LC_ALL, "chs");
        char * multiByte = "我是忧伤的多字节字符串。";
        wchar_t * wideChar = L"我是忧伤的宽字符字符串。";
        // 在使用 W2A 和 A2W 宏之前需要加 USES_CONVERSION
        USES_CONVERSION;
        // W2A 完成宽字符到多字节的转换
        cout << W2A(wideChar) << endl;
        // A2W 完成多字节到宽字符的转换
        wcout << A2W(multiByte) << endl;
    
        system("pause");
        return 0;
    }