bl双性强迫侵犯h_国产在线观看人成激情视频_蜜芽188_被诱拐的少孩全彩啪啪漫画

VSCode調(diào)試C/C++項(xiàng)目-創(chuàng)新互聯(lián)

最近寫完了自己的操作系統(tǒng),深感有一個(gè)方便的調(diào)試環(huán)境是有多么重要,能夠提升不少開發(fā)效率。恰好最近在的技術(shù)交流群里群友在問如何搭建VSCode調(diào)試操作系統(tǒng)的環(huán)境,剛考完試,就先把這篇VSCode調(diào)試C/C++的通用教程發(fā)出來,而后針對(duì)VSCode調(diào)試操作系統(tǒng)的特定環(huán)境的教程稍后再發(fā)出來

使用VSCode來調(diào)試C/C++工程

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡(jiǎn)單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名與空間、虛擬空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、溫州網(wǎng)站維護(hù)、網(wǎng)站推廣。VSCode調(diào)試C/C++項(xiàng)目

VS Code作為宇宙第一編輯器,在眾多插件的加持下,具有了調(diào)試、單元測(cè)試等等功能,使其越來越像一個(gè)IDE。

然而很多人其實(shí)并不會(huì)使用VS Code的調(diào)試功能,只是把VS Code當(dāng)做了一個(gè)帶有語(yǔ)法補(bǔ)全的編輯器。這實(shí)際上極大地浪費(fèi)了VS Code的功能,尤其是對(duì)于C/C++開發(fā)者來說,使用命令行的GDB調(diào)試遠(yuǎn)不如使用VS Code內(nèi)嵌的GDB圖形化界面調(diào)試來的舒服。

本文就講介紹如何使用VS Code調(diào)試C/C++項(xiàng)目

1. 概述

通常我們?cè)谡{(diào)試一個(gè)C/C++工程的時(shí)候,大體上的流程可以分為兩步:

  • 啟動(dòng)調(diào)試器(GDB)前的準(zhǔn)備工作
  • 啟動(dòng)調(diào)試器(GDB)進(jìn)行調(diào)試

例如對(duì)于一個(gè)CMake組織的C/C++項(xiàng)目,這兩大步具體包含的流程如下(編寫CMakeLists.txt是在編碼階段,編碼是與調(diào)試獨(dú)立的階段):

  • 啟動(dòng)調(diào)試器(GDB)前的準(zhǔn)備工作
    1. 創(chuàng)建build文件夾:mkdir -p build
    2. 切換到build文件夾:cd build
    3. 配置(Configure)項(xiàng)目:cmake ..
    4. 構(gòu)建/編譯(Build)項(xiàng)目:make
  • 啟動(dòng)調(diào)試器(GDB)進(jìn)行調(diào)試
    1. 啟動(dòng)調(diào)試器:gdb

對(duì)于不同的項(xiàng)目(npm項(xiàng)目、C#項(xiàng)目、java項(xiàng)目等等),可能在啟動(dòng)調(diào)試器前的準(zhǔn)備工作不同,但是大體上都可以分為進(jìn)行調(diào)試前需要進(jìn)行的一系列任務(wù),以及結(jié)合具體參數(shù)啟動(dòng)時(shí)調(diào)試器

因此,對(duì)于這兩個(gè)階段,VSCode中提供了tasks.jsonlaunch.json兩個(gè)文件來分別描述調(diào)試前的準(zhǔn)備工作以及以指定的參數(shù)啟動(dòng)調(diào)試器

2. 調(diào)試前的準(zhǔn)備工作:tasks.json

VSCode使用tasks.json來描述啟動(dòng)調(diào)試前的準(zhǔn)備工作。

A. tasks.json的結(jié)構(gòu)

tasks.json的結(jié)構(gòu)一般如下

{"version": "2.0.0",
    "tasks": [

    ],
    "inputs": [

    ]
}

tasks.json的一般結(jié)構(gòu)

B. version標(biāo)簽

version標(biāo)簽指定了Tasks.json的版本,因?yàn)椴煌陌姹局С值臉?biāo)簽不一樣,所以需要使用version標(biāo)簽指明版本。

目前version支持2.0.0版本,所以直接指定version2.0.0即可。

C. tasks標(biāo)簽

tasks標(biāo)簽是一個(gè)列表,我們?cè)谄渲卸x不同的task,而關(guān)于具體的task如何定義則見下

我們以創(chuàng)建build文件夾這個(gè)任務(wù)為例

{"label": "create dir",
		"type": "shell",
		"command": "mkdir",
		"args": [
		    "-p",
		    "build"
		],
		"windows": {"args": [
		        "-Force",
		        "build"
		    ],
		    "options": {"shell": {"executable": "powershell.exe"
		        }
		    },
		}
}
1) label標(biāo)簽

label標(biāo)簽定義了一個(gè)任務(wù)的名字,稍后我們能用通過名字取定位一個(gè)任務(wù),從而實(shí)現(xiàn)諸如將多個(gè)任務(wù)合并為一個(gè)組,而后執(zhí)行一組任務(wù)這樣的操作。

label標(biāo)簽的值是隨我們自己喜歡,想寫什么就寫什么的。

2) type標(biāo)簽

type標(biāo)簽指定了一個(gè)任務(wù)的類型。所有的任務(wù)大致上可以分為兩類:

  • 第一類就是在Shell中執(zhí)行的命令,值為shell
  • 第二類就是一個(gè)進(jìn)程,例如我們寫的程序是操作MySQL數(shù)據(jù)庫(kù)的程序,那么就需要在調(diào)試前啟動(dòng)MySQL數(shù)據(jù)庫(kù),則此時(shí)MySQL數(shù)據(jù)庫(kù)就是進(jìn)程形式的任務(wù)。進(jìn)程形式的任務(wù)的值為process
3 ) command標(biāo)簽

command標(biāo)簽指定了需要執(zhí)行的命令或者程序。

  • 如果是Shell中的命令的話,那么command的值為需要執(zhí)行的命令。
  • 如果是進(jìn)程的話,那么command的值為需要執(zhí)行的可執(zhí)行程序的位置,可執(zhí)行程序可以是有x權(quán)限的.sh,也可以是.exe等可執(zhí)行程序。
4 ) args標(biāo)簽

args標(biāo)簽指定了執(zhí)行的命令或者程序時(shí)傳入的命令行參數(shù)。在具體執(zhí)行的時(shí)候會(huì)把多個(gè)參數(shù)用空格連接起來而后執(zhí)行。

結(jié)合command標(biāo)簽,我們執(zhí)行的命令就是下面這句話

mkdir -p build
5 ) windows標(biāo)簽

windows標(biāo)簽指定了只有在windows系統(tǒng)上的配置。我們?cè)?code>windows標(biāo)簽中指定了兩個(gè)標(biāo)簽options標(biāo)簽和args標(biāo)簽。

  • 對(duì)于args標(biāo)簽就意味著在其他系統(tǒng)(Linux/MacOS)上,使用-p build作為命令行參數(shù),而在Windows系統(tǒng)上,使用-Force build作為命令行參數(shù)。

    這是因?yàn)樵?code>Linux/MacOS系統(tǒng)上,創(chuàng)建一個(gè)文件夾使用下面的命令就行了

    mkdir -p 文件夾名

    但是在Windows平臺(tái)上,創(chuàng)建一個(gè)文件夾需要使用下面的命令

    mkdir -Force 文件夾名
  • 對(duì)于options標(biāo)簽就意味著只有在Windows平臺(tái)上才會(huì)有這個(gè)標(biāo)簽。

6 ) options標(biāo)簽

options標(biāo)簽指明了運(yùn)行命令的shell的位置(shell標(biāo)簽)、運(yùn)行命令的環(huán)境變量(env標(biāo)簽)以及運(yùn)行命令的文件夾(cwd標(biāo)簽)。當(dāng)然這里只用了shell這一個(gè)標(biāo)簽。

使用shell標(biāo)簽的原因是因?yàn)樵?code>Windows上有兩個(gè)命令行,一個(gè)是cmd一個(gè)是powershell。而mkdir這個(gè)命令是在powershell中的,因此我們需要特殊指明在Windwos上需要使用powershell.exe作為Shell的解釋器

D. input標(biāo)簽

input標(biāo)簽用于生成一個(gè)選項(xiàng)卡,接收用戶的輸入,一般是和args標(biāo)簽一起使用我們稍后再講解這個(gè)標(biāo)簽的用法。

3. 啟動(dòng)調(diào)試器:launch.json A. launch.json的結(jié)構(gòu)

launch.json的結(jié)構(gòu)一般如下

{// 使用 IntelliSense 了解相關(guān)屬性。 
    // 懸停以查看現(xiàn)有屬性的描述。
    // 欲了解更多信息,請(qǐng)?jiān)L問: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {"type": "gdb",
            "request": "launch",
            "name": "GDB",
            "program": "${workspaceFolder}/${command:AskForProgramName}",
            "stopOnEntry": true,
            "arguments": "",
            "preLaunchTask": ""
        }
    ]
}

launch.json的結(jié)構(gòu)

B. version標(biāo)簽

launch.json中的version標(biāo)簽和tasks.json中的version標(biāo)簽作用是一樣的,一般都用0.2.0

C. configuration標(biāo)簽

configuration標(biāo)簽中定義了開始啟動(dòng)調(diào)試器時(shí)候的具體的配置信息。具體來說,可以有多套配置信息。即configuration標(biāo)簽下可以有多個(gè)條目。

1 ) name標(biāo)簽

name標(biāo)簽定義了一套配置信息的名稱,這個(gè)名稱稍后可以在左邊的運(yùn)行與調(diào)試頁(yè)面中看到。

name定義調(diào)試的配置名稱,可以在運(yùn)行與調(diào)試界面選擇需要運(yùn)行的配置

2 ) type標(biāo)簽

type標(biāo)簽指定了調(diào)試時(shí)啟動(dòng)的調(diào)試器:

  • 對(duì)于C/C++項(xiàng)目來說,type的值指定為cppdbg或者是cppvsdbg

    • Windows上開發(fā)一般用的編譯器都是Visual Studio中自帶的msvc編譯器,適用的調(diào)試器也是Visual Studio自帶的,此時(shí)就需要把值設(shè)為cppvsdbg
    • ``Linux上用的一般都是gccMacOS上用的編譯器一般都是clang,對(duì)應(yīng)的調(diào)試器分別是gdblldb,此時(shí)需要把值設(shè)為cppdbg
  • 對(duì)于Python項(xiàng)目來說,type的值指定為python,因?yàn)?code>python解釋器自帶了pdb這個(gè)調(diào)試器

剩下的具體查詢手冊(cè):https://code.visualstudio.com/docs

3 ) request標(biāo)簽

request標(biāo)簽指明了調(diào)試器調(diào)試程序的方式。具體來說有兩種:

  • launch:表示調(diào)試器直接啟動(dòng)程序進(jìn)行調(diào)試,類似于使用命令gdb helloworld,將會(huì)直接運(yùn)行命令helloworld
  • attach:有時(shí)候,我們需要調(diào)試的程序運(yùn)行在遠(yuǎn)程服務(wù)器上,此時(shí)在服務(wù)器上已經(jīng)運(yùn)行了一個(gè)gdb,而且服務(wù)器上的gdb把調(diào)試服務(wù)暴露在某一個(gè)端口上,此時(shí)我們?cè)诒緳C(jī)上運(yùn)行gdb的時(shí)候,通過鏈接遠(yuǎn)程服務(wù)器該端口,從而實(shí)現(xiàn)用本地的gdb調(diào)試遠(yuǎn)程服務(wù)器上的程序。此時(shí),遠(yuǎn)程服務(wù)器上的gdb稱為gdb server。這種調(diào)試方式稱為attach,即把調(diào)試器附加到一個(gè)gdb server上去。

一般在本機(jī)做調(diào)試的時(shí)候值都是launch

4 ) program標(biāo)簽

program標(biāo)簽指定了我們需要調(diào)試的程序。注意,如果request標(biāo)簽的值是attach的話,那么就不能使用program標(biāo)簽。

5 ) workspaceFolder宏

CMake中有EXECUTABLE_OUTPUT_PATH宏,我們可以指定EXECUTABLE_OUTPUT_PATH宏的值從而指定可執(zhí)行文件輸出的路徑,也可以通過${}來讀取EXECUTABLE_OUTPUT_PATH宏的值來打印到屏幕上或者用于為其他宏賦值。

類似的,VSCode中也有功能類似的宏,workspaceFolder這個(gè)宏就表示了當(dāng)前打開的目錄。我們也可以使用${}來獲取這個(gè)宏的值。

6 ) command:AskForProgramName

command:AskForProgramName這個(gè)宏的作用就是在程序運(yùn)行的時(shí)候在上面彈出來一個(gè)選項(xiàng)卡,詢問用戶需要調(diào)試的程序的名字。

例如我們直接對(duì)著launch.json這個(gè)程序按下F5,然后就會(huì)彈出來一個(gè)選項(xiàng)卡讓我們輸入需要調(diào)試的程序的名字

VSCode彈出選項(xiàng)卡要求用戶輸入程序的名字

7 ) stopAtEntry標(biāo)簽

stopAtEntry標(biāo)簽表示在進(jìn)入到主程序之后就會(huì)停下來,對(duì)于C/C++來說就是在進(jìn)入main之后就停下來。

但是一般我們都是打上斷點(diǎn),然后直接運(yùn)行到斷點(diǎn)處,所以這個(gè)stopAtEntry的值一般用的都是false

8 ) Arguments標(biāo)簽

這個(gè)標(biāo)簽我沒用過,所以我也搞不清楚,如果要傳參的給程序的話,用args標(biāo)簽

9 ) preLaunchTask標(biāo)簽

preLaunchTask標(biāo)簽可以說是最重要的標(biāo)簽之一,它溝通了launch.jsontasks.json這兩個(gè)文件。

前面我們?cè)?code>tasks.json中定義了一系列任務(wù),而launch.json中的這個(gè)標(biāo)簽說明了在啟動(dòng)調(diào)試器前需要執(zhí)行的tasks.json中的那個(gè)任務(wù)。

所以利用這個(gè)標(biāo)簽,我們就可以實(shí)現(xiàn)從調(diào)試前的準(zhǔn)備工作再到啟動(dòng)調(diào)試器這一連串的任務(wù)。

4. 一個(gè)Toy Example: echo 宏

下面展示一個(gè)Toy Example來展示tasks.jsonlaunch.json的workflow

A. tasks.json的內(nèi)容

Toy Example中tasks.json的內(nèi)容如下

{"version": "2.0.0",
    "tasks": [
        {"label": "example",
            "command": "echo",
            "args": [
                "${file}\n",
                "${fileBasename}\n",
                "${fileBasenameNoExtension}\n",
                "${fileDirname}\n",
            ]
        }
    ]
}

具體來說我們就是想要執(zhí)行一下下面的命令

echo "${file}\n" "${fileBasename}\n" "${fileBasenameNoExtension}\n" "${fileDirname}\n"

主要是看一看這四個(gè)宏的值分別是什么

B. launch.json的內(nèi)容

Toy Example中lauch.json的內(nèi)容如下

{// 使用 IntelliSense 了解相關(guān)屬性。 
    // 懸停以查看現(xiàn)有屬性的描述。
    // 欲了解更多信息,請(qǐng)?jiān)L問: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {"type": "gdb",
            "request": "launch",
            "name": "Toy Example",
            "program": "${workspaceFolder}/${file}",
            "stopOnEntry": true,
            "preLaunchTask": "example"
        }
    ]
}

具體來說就是在啟動(dòng)調(diào)試器之前運(yùn)行一下上面定義的example這個(gè)task。

C. hello_world.c

我們接下來寫一個(gè)hello_world.c,里面的內(nèi)容如下:

#includeint main(int argc, char *argv[]){for (int i = 0; i< argc; i++)
        printf("%s\n", argv[i]);
    printf("Hello World!\n");

    return 0;
}
D. 開始調(diào)試

首先在運(yùn)行和調(diào)試界面把調(diào)試的配置選定為Toy Example,然后編輯器打開hello_world.c

開始調(diào)試

接下來按F5開始調(diào)試

此時(shí)我們?cè)诮K端就能夠看到執(zhí)行的任務(wù)以及輸出

執(zhí)行的任務(wù)以及輸出

很清楚就能看到,上面四個(gè)宏的值分別是

${file}													:		/Users/jack/project/test/vscode_test/hello_world.c
${fileBasename}									:		hello_world.c
${fileBasenameNoExtension}			:		hello_world
${fileDirname}									:		/Users/jack/project/test/vscode_test
5. 一個(gè)Toy Example:編譯文件

我們對(duì)上面的Toy Example進(jìn)行修改,增加一個(gè)自動(dòng)編譯的功能

A. tasks.json的內(nèi)容

我們給tasks.json新加一個(gè)task,即自動(dòng)編譯,此外我們修改一下輸出宏的task

{"version": "2.0.0",
    "tasks": [
        {"label": "echo",
            "command": "echo",
            "args": [
                "${file}\n",
                "${pathSeparator}\n",
                "${fileBasenameNoExtension}\n",
                "${fileDirname}${pathSeparator}${fileBasenameNoExtension}.o\n"
            ]
        },
        {"label": "build",
            "command": "gcc",
            "args": [
                "${file}",
                "-o",
                "${fileDirname}${pathSeparator}${fileBasenameNoExtension}.o"
            ]
        }
    ]
}
B. launch.json的內(nèi)容

我們?cè)俳olaunch.json中新加一個(gè)配置信息

{// 使用 IntelliSense 了解相關(guān)屬性。 
    // 懸停以查看現(xiàn)有屬性的描述。
    // 欲了解更多信息,請(qǐng)?jiān)L問: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {"type": "gdb",
            "request": "launch",
            "name": "Echo Macros",
            "program": "${workspaceFolder}/${file}",
            "stopOnEntry": true,
            "preLaunchTask": "echo"
        },
        {"type": "gdb",
            "request": "launch",
            "name": "Gcc Compile",
            "program": "${workspaceFolder}${pathSeparator}${fileBasenameNoExtension}.o",
            "stopAtEntry": false,
            "preLaunchTask": "build"
        }
    ]
}
C. 運(yùn)行Echo Macros

首先輸出一下在build這個(gè)task中使用到的宏。

具體來說在運(yùn)行和調(diào)試界面選擇配置為Echo Macros

選擇配置為Echo Macros

然后按下F5開始運(yùn)行

Echo Macros運(yùn)行的結(jié)果

可以看到,上面四個(gè)宏的值是

${file}																												:		/Users/jack/project/test/vscode_test/hello_world.c
${pathSeparator}																							:		/
${fileBasenameNoExtension}																		:		hello_world
${fileDirname}${pathSeparator}${fileBasenameNoExtension}.o		:		/Users/jack/project/test/vscode_test/hello_world.o
D. 運(yùn)行Gcc Compile

接下來我們運(yùn)行Gcc Compile,類似的,還是先在運(yùn)行和調(diào)試界面選擇Gcc Compile,然后按下F5開始運(yùn)行

選擇配置為Gcc Compile

而后我們就會(huì)發(fā)現(xiàn)在文件夾下就出現(xiàn)了編譯后的文件

Gcc Compile運(yùn)行的結(jié)果

6. 一個(gè)Toy Example:調(diào)試程序

我們上面做到了編譯程序,而在編譯之后我們需要干的就是去調(diào)試這個(gè)程序。

首先需要明白的是,我們?nèi)绻胍褂?code>gdb、lldb等調(diào)試器去調(diào)試一個(gè)程序的時(shí)候,我們必須要在編譯的時(shí)候指定-g參數(shù),這樣編譯器(例如gccclang)在編譯的時(shí)候就會(huì)把源代碼、符號(hào)表等等信息寫入到程序里面去。

而后在調(diào)試的時(shí)候,我們使用命令gdb xxxx/lldb xxxxgdb/lldb就回去讀取源代碼和符號(hào)表,從而開始調(diào)試。

A. tasks.json的內(nèi)容

我們首先新增加一個(gè)名為debug_build的task,具體來說就是在編譯的時(shí)候加上-g參數(shù)

{"version": "2.0.0",
    "tasks": [
        {"label": "debug_build",
            "command": "gcc",
            "args": [
                "${file}",
                "-g",
                "-o",
                "${fileDirname}${pathSeparator}${fileBasenameNoExtension}.o"
            ]
        }
    ]
}
B. launch.json的內(nèi)容

為了要進(jìn)行debug,我們?cè)趌aunch.json中新加入一項(xiàng),這一項(xiàng)可能會(huì)有些復(fù)雜

{// 使用 IntelliSense 了解相關(guān)屬性。 
    // 懸停以查看現(xiàn)有屬性的描述。
    // 欲了解更多信息,請(qǐng)?jiān)L問: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {"type": "cppdbg",
            "request": "launch",
            "name": "LLDB Debug",
            "program": "${workspaceFolder}${pathSeparator}${fileBasenameNoExtension}.o",
            "stopAtEntry": false,
            "preLaunchTask": "debug_build",
            "cwd": "${workspaceFolder}",
            "MIMode": "lldb"
        },
    ]
}

詳細(xì)的解釋如下:

  • "type":"cppdbg":新加入的這一項(xiàng)的類型是cppdgb,表示C/C++ Debug。因?yàn)槲覀冃绿砑拥倪\(yùn)行配置的目的就是給C/C++程序Debug,所以我們讓這一項(xiàng)的類型是cppdgb。如果我們是別的項(xiàng)目的話,例如是node.js的項(xiàng)目的話,那么我們讓這個(gè)運(yùn)行配置的typenode即可
  • "cwd:"${workspaceFolder}":因?yàn)樵陂_始調(diào)試的時(shí)候我們需要在指定的文件夾下運(yùn)行調(diào)試器,所以就需要使用cwd標(biāo)簽指定工作目錄,一般制定成項(xiàng)目的根目錄,也就是workspaceFolder就行了
  • "MIMode":"lldb":不同的系統(tǒng)上使用的調(diào)試器不同,MacOSLinuxWindows使用的調(diào)試器分別是lldbgdbmsvc/gdbmsvcVisual Studio帶的調(diào)試器,gdbMinGW帶的調(diào)試器),所以我們需要使用MIMode標(biāo)簽指定使用的調(diào)試器的類型。

此外,我們其實(shí)還可以使用miDebuggerArgsmiDebuggerPath來專門制定調(diào)用調(diào)試器時(shí)候傳入的參數(shù)以及調(diào)試器的路徑。

因?yàn)槲覍戇@篇博客時(shí)候用的是Mac,所以用的調(diào)試器就是LLDB

C. 運(yùn)行LLDB Debug

我們給前面的程序加上一個(gè)斷點(diǎn),然后選擇運(yùn)行配置為LLDB Debug,然后按下F5開始調(diào)試。

運(yùn)行LLDB Debug

接下來我們就進(jìn)入了調(diào)試頁(yè)面:

  • 下方:顯示了所有的任務(wù)
  • 左側(cè):顯示了當(dāng)前所有的變量以及變量的值、監(jiān)視的變量以及表達(dá)式、函數(shù)的調(diào)用堆棧
  • 中間:顯式了正在調(diào)試的程序
  • 上方:顯式了調(diào)試的功能按鈕

image-20221011184352703

7. 一個(gè)Toy Example:順序執(zhí)行

我們上面調(diào)試了一個(gè)程序。但是在現(xiàn)實(shí)中,我們往往在調(diào)試前是需要順序執(zhí)行多個(gè)命令的,而不是簡(jiǎn)單的編譯。

我們接下來給出的Toy Example在啟動(dòng)調(diào)試前就將順序執(zhí)行兩步命令:

  • 創(chuàng)建一個(gè)bin文件夾
  • 將編譯好的源文件輸出到bin文件夾中
A. tasks.json中的內(nèi)容

我們?cè)?code>tasks.json中創(chuàng)建下面的兩個(gè)任務(wù)

{"version": "2.0.0",
    "tasks": [
        {"label": "create_bin",
            "type": "shell",
            "command": "mkdir",
            "args": [
                "-p",
                "${workspaceFolder}${pathSeparator}/bin"
            ]
        },
        {"label": "debug_build",
            "type": "shell",
            "command": "gcc",
            "group": "build",
            "args": [
                "${file}",
                "-g",
                "-o",
                "${fileDirname}${pathSeparator}bin${pathSeparator}${fileBasenameNoExtension}.o"
            ],
            "dependsOn": "create_bin",
        },
    ],
}

create_bin任務(wù)就是老三樣,沒啥好說的。

關(guān)鍵就在于修改之后的debug_build任務(wù),debug_build任務(wù)中新增加了一個(gè)dependsOn標(biāo)簽,這個(gè)標(biāo)簽說明了在運(yùn)行debug_build任務(wù)之前需要運(yùn)行的任務(wù)。

在這里就表示在運(yùn)行debug_build任務(wù)之前,需要運(yùn)行create_bin任務(wù)。

B. launch.json的內(nèi)容

launch.json中的內(nèi)容保持不變,還是LLDB Debug

{// 使用 IntelliSense 了解相關(guān)屬性。 
    // 懸停以查看現(xiàn)有屬性的描述。
    // 欲了解更多信息,請(qǐng)?jiān)L問: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {"type": "cppdbg",
            "request": "launch",
            "name": "LLDB Debug",
            "program": "${workspaceFolder}${pathSeparator}bin${pathSeparator}${fileBasenameNoExtension}.o",
            "stopAtEntry": false,
            "preLaunchTask": "debug_build",
            "cwd": "${workspaceFolder}",
            "MIMode": "lldb",
        },
    ]
}
C. 運(yùn)行LLDB Debug

運(yùn)行LLDB Debug的結(jié)果如下,可以發(fā)現(xiàn)首先bin文件被創(chuàng)建了,接著可執(zhí)行文件輸出到了bin文件夾中,而后開始debug

image-20221011222558619

8. 一個(gè)真實(shí)的例子:CMake工程

我們上面講了四個(gè)Toy Example,介紹了VSCode的tasks.jsonlaunch.json最基本的功能,接下來我們就把這些功能結(jié)合到一起,用VSCode調(diào)試一個(gè)真實(shí)的CMake工程。

下面這個(gè)工程的目的就是編譯就是一個(gè)名為Wish的自己寫的shell腳本的項(xiàng)目,編譯完成后將在本機(jī)得到一個(gè)可以運(yùn)行的shell

A. CMake工程結(jié)構(gòu)及文件

CMake工程的結(jié)構(gòu)如下

tree ./
./
├── CMakeLists.txt
├── main.c
├── wish.c
└── wish.h

0 directories, 4 files

項(xiàng)目的源文件一共有四個(gè),其中:

  • CMakeLists.txt定義了項(xiàng)目結(jié)構(gòu)
  • wish.cwish.h定義了libwish靜態(tài)庫(kù)
  • main.c調(diào)用了libwish庫(kù)
1 ) CMakeLists.txt

CMakeLists.txt中的內(nèi)容如下:

project(WISH)

cmake_minimum_required(VERSION 3.9)

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

add_library(
    libwish STATIC
    wish.c
)

add_executable(
    wish
    main.c
)

target_link_libraries(wish libwish)
2 ) wish.h 和 wish.c
// wish.h

#ifndef _WISH_H

#include#include#include#include#include#include#include#include#include#include#define _WISH_H 1
#define WISH_EXIT_SUCCESS 0
#define WISH_EXIT_FAILURE -1
#define WISH_BUF_SIZE 1024
#define WISH_MY_SEARCH 1
#define WISH_BUILTIN_NUM sizeof(builtin_str) / sizeof(char *)
#define WISH_MAX_WORD 20
#define WISH_MAX_FNAME 1024
#define WISH_MAX_PATH 128

#define WISH_DEBUG 1

// Base Functions
void wish_loop(void);                                   // main loop of wish
char *wish_read_line(void);                             // read user input line
char **wish_split_line(char *line);                     // split user input line into words
int wish_redirection(char *args[]);                     // parse user input token to 
int wish_execute(char *args[], int rarg_sht);           // execute user input command
int wish_launch(char *args[], int rarg_sht);            // launch other program
char *wish_search(char *cmd);                           // search program in PATH
void wish_error();                                      // report error
void wish_line(int);

int wish_cd(char **args);
int wish_exit(char **args);
int wish_path(char **args);
int wish_help(char **args);
int wish_env(char **args);

#endif
// wish.c
#include "wish.h"

// path
char *path[WISH_MAX_PATH] = {[0] = "/bin"
};

// Shell Builtin Funtions
char *builtin_str[] = {"cd",
    "exit",
    "path",
    "help",
    "wenv"
};

int (*builtin_func[])(char **) = {&wish_cd,
    &wish_exit,
    &wish_path,
    &wish_help,
    &wish_env
};


void wish_loop(void){char *line;
    char **args;
    bool status;
    do
    {printf("wish>");
        line = wish_read_line();
        args = wish_split_line(line);
        int i = -1;
        if ((i = wish_redirection(args)) != -1){args[i++] = 0;
        }
        status = wish_execute(args, i);

        free(line);
        for (int i = 0; i< WISH_MAX_WORD; i++)
            if (NULL != args[i])
                free(args[i]);
        free(args);
    } while (status);
    if (!status)
        exit(WISH_EXIT_FAILURE);
    return;
}

char *wish_read_line(void){int position = 0;
    int bufsize = WISH_BUF_SIZE;
    char * buffer = (char *) malloc(sizeof(char) * bufsize);
    if(NULL == buffer){fprintf(stderr, "wish: Memory allocation failed for read line.\n");
        exit(WISH_EXIT_FAILURE);
    }
    char c;
    while (true)
    {// read a char
        c = getchar();
        if (c == EOF || c == '\n'){buffer[position++] = '\0';
            return buffer;
        } else
            buffer[position++] = c;
        // resize buffer
        if (position >= bufsize){bufsize += WISH_BUF_SIZE;
            char * temp = (char *) malloc(sizeof(char) * bufsize);
            if(NULL == temp){fprintf(stderr, "wish: Memory allocation failed for read line.\n");
                exit(EXIT_FAILURE);
            }
            // copy and reset pointer to new buffer
            int num = position;
            while (num >0){temp[num] = buffer[num];
                num--;
            }
            free(buffer);
            buffer = temp;
        }
    }
}


char **wish_split_line(char *line){char **words = (char **) malloc(sizeof(char *) * WISH_MAX_WORD);
    for (int i = 0; i< WISH_MAX_WORD; i++)
        words[i] = (char *)0;
    
    int j = 0, k = 0;
    int len = strlen(line);
    char *temp = (char *) malloc(sizeof(char) * len);
    for (int i = 0; i< len + 1; i++){temp[j] = line[i];
        if (temp[j] == ' ' || temp[j] == '\t' || temp[j] == '\0'){temp[j] = '\0';
            words[k] = (char *) malloc(sizeof(char) * (i + 1));
            strncpy(words[k], temp, i);
            words[k][i] = '\0';
            j = 0, k += 1;
        } else 
            j += 1;
    }
    free(temp);
    return words;
}

int wish_redirection(char **args){int i = 0;
    while (args[i] != 0)
    {if (args[i][0] == '>')
            return i;
        i += 1;
    }
    return -1;
}

int wish_execute(char *args[], int rarg_sht){if (NULL == args[0])
        return 1;
    // run builtin command
    for (int i = 0; i< WISH_BUILTIN_NUM; i++)
        if (strcmp(args[0], builtin_str[i]) == 0)
            return (*builtin_func[i])(args);
    return wish_launch(args, rarg_sht);
}

int wish_launch(char *args[], int rarg_sht){// search path
    int i = 0;
    char *executable_path = (char *) malloc(sizeof(char) * WISH_MAX_FNAME);
    for (int j = 0; j< WISH_MAX_FNAME; j++)
        executable_path[j] = '\0';
    char * temp_path = (char *) malloc(sizeof(char) * WISH_MAX_FNAME);
    while (path[i] != NULL) {// copy path[i] to temp and then concate
        if (strncpy(temp_path, path[i], strlen(path[i])) == NULL){wish_error();
            wish_line(__LINE__);
            return WISH_EXIT_FAILURE;
        }
        int len = strlen(temp_path);
        temp_path[len] = '/';
        temp_path[len + 1] = '\0';
        if (strcat(temp_path, args[0]) == NULL){wish_error();
            wish_line(__LINE__);
            return WISH_EXIT_FAILURE;
        }
        // check privilege
        if(access(temp_path, X_OK) == 0){if (strcpy(executable_path, temp_path) == NULL){wish_error();
                wish_line(__LINE__);
                return WISH_EXIT_FAILURE;
            }
            break;
        }
        i++;
    }
    free(temp_path);
    // print error if not found
    if (executable_path[0] == '\0'){free(executable_path);
        wish_error();
        wish_line(__LINE__);
        return EXIT_FAILURE;
    }
    int status;
    pid_t son_pid, wait_pid;
    son_pid = fork();
    if (son_pid == 0){// child process
        // redirection
        if (-1 != rarg_sht && NULL != args[rarg_sht]){// get real path
            char rp[WISH_MAX_FNAME];
            realpath(args[rarg_sht], rp);
            if (NULL == freopen(rp, "w", stdout))
                fprintf(stderr, "wish: redirection file %s open fail!\n", rp);
        }
        // run cmd
        int (*func)();
        if (WISH_MY_SEARCH == 1)
            func = execvp;
        else
            func = execv;
        if (func(args[0], args) == -1){// wish_error();
            // wish_line(__LINE__);
            return 1;
        }
        // if run the following code, then it is wrong
        exit(WISH_EXIT_FAILURE);
    } else if (son_pid< 0) 
        perror("wish: son process create fail by fork");
    else {do {wait_pid = waitpid(son_pid, &status, WUNTRACED);
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }
    return true;
}

char *wish_search(char *cmd){return (char *)0;
}

void wish_error(){char *err_msg = "An error has occurred\n";
    write(STDERR_FILENO, err_msg, strlen(err_msg));
}

void wish_line(int lineno){#ifdef WISH_DEBUG
        fprintf(stderr, "in line %d\n", lineno);
    #endif
}

int wish_cd(char *args[]){if (NULL == args[1])
        wish_error();
    else
        if (chdir(args[1]) != 0)
            wish_error();
    return 1;
}

int wish_exit(char *args[]){if (NULL == args[1])
        exit(0);
    wish_error();
    return 1;
}

int wish_path(char *args[]){int i = 0;
    while ((path[i] = args[i+1]) != NULL)
        i++;    
    return 1;
}

int wish_env(char *args[]){if (NULL == args[1])
        return 1;
    else {char * env = getenv(args[1]);
        printf("%s:\n", args[1]);
        printf("%s\n", env);
    }
    return 1;
}

int wish_help(char *args[]){printf("WISH written by Shihong Wang.\n");
    printf("Usage: command  argument [enter]\n");
    printf("Builtin commands:\n");
    for (int i = 0; i< WISH_BUILTIN_NUM; i++)
        printf("\t%s", builtin_str[i]);
    printf("\nRefer man page of other command.\n");
    return 1;
}
3 ) main.c
#include "wish.h"

extern char **environ;

int main(int argc, char *argv[])
{if (argc == 1)
    {// command loop mode
        wish_loop();
    }
    else
    {// read-parse-execute mode
        FILE *file;
        if (NULL == (file = fopen(argv[1], "r")))
        {fprintf(stderr, "wish: read-parse mode %s file not exists!\n", argv[1]);
            exit(WISH_EXIT_FAILURE);
        }
        // read a line, parse and execute
        int status;
        char ** args;
        size_t len = 0;
        ssize_t read;
        char *line = (char *) malloc(sizeof(char) * WISH_BUF_SIZE);
        while ((read = getline(&line, &len, file)) != -1)
        {int j = -1;
            while (line[++j] != '\n');
            line[j] = '\0';
            args = wish_split_line(line);
            int i = -1;
            if ((i = wish_redirection(args)) != -1){args[i++] = 0;
            }
            status = wish_execute(args, i);
            for (int i = 0; i< WISH_MAX_WORD; i++)
                if (NULL != args[i])
                    free(args[i]);
            free(args);
        }
        free(line);
    }
    return WISH_EXIT_SUCCESS;
}
B. tasks.json的內(nèi)容

我們?cè)谡{(diào)試前,需要:

  • 創(chuàng)建build文件夾
  • 進(jìn)入build文件夾使用cmake配置項(xiàng)目
  • 使用make或者cmake --build ./ --target all進(jìn)行編譯

因此,我們需要再tasks.json中定義三個(gè)任務(wù)

{"version": "2.0.0",
    "tasks": [
        {"label": "create_build",
            "type": "shell",
            "command": "mkdir",
            "args": [
                "-p",
                "${workspaceFolder}/build"
            ],
            "detail": "創(chuàng)建build文件夾",
        },
        {"label": "cmake_configure",
            "type": "shell",
            "command": "cmake",
            "options": {"cwd": "${workspaceFolder}/build"
            },
            "args": [
                "-DCMAKE_BUILD_TYPE=${input:CMAKE_BUILD_TYPE}",
                "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", // 生成compile_commands.json 供c/c++擴(kuò)展提示使用
                "../"
            ],
            "dependsOn": "create_build",
            "detail": "CMake配置項(xiàng)目"
        },
        {"label": "make_build",
            "type": "shell",
            "command": "make",
            "options": {"cwd": "${workspaceFolder}/build"
            },
            "args": [
                "all"
            ],
            "dependsOn": "cmake_configure",
            "detail": "Make構(gòu)建項(xiàng)目"
        }
    ],

    "inputs": [
        {"id": "CMAKE_BUILD_TYPE",
            "type": "pickString",
            "description": "選擇項(xiàng)目的編譯類型(CMake Build Type)",
            "options": [
                "Debug",
                "Release",
                "RelWithDebInfo",
                "MinSizeRel",
            ],
            "default": "Debug"
        }
    ]
}

關(guān)于input標(biāo)簽,參考手冊(cè)的這一節(jié):https://code.visualstudio.com/docs/editor/variables-reference

C. launch.json的內(nèi)容

launch.json的內(nèi)容如下

{// 使用 IntelliSense 了解相關(guān)屬性。 
    // 懸停以查看現(xiàn)有屬性的描述。
    // 欲了解更多信息,請(qǐng)?jiān)L問: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {"type": "cppdbg",
            "request": "launch",
            "name": "LLDB Debug",
            "program": "${workspaceFolder}/bin/wish",
            "stopAtEntry": true,
            "preLaunchTask": "make_build",
            "cwd": "${workspaceFolder}",
            "MIMode": "lldb",
        },
    ]
}
D. 開始調(diào)試

按下F5開始調(diào)試

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-9m1QUYpQ-1671721662119)(https://jack-1307599355.cos.ap-shanghai.myqcloud.com/%E5%B1%8F%E5%B9%95%E5%BD%95%E5%88%B62022-10-11-%E4%B8%8B%E5%8D%8811.28.15%20(1)].gif)

9. CMake工程常用的tasks.json和launch.json

下面給出一個(gè)CMake工程常用的tasks.jsonlaunch.json

// tasks.json
{// See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {// 在根文件夾中執(zhí)行創(chuàng)建文件夾build的命令
            // 除windows系統(tǒng)外執(zhí)行的命令為`mkdir -p build`
            // windows系統(tǒng)是在powershell中執(zhí)行命令`mkdir -Force build`
            "label": "build_dir",
            "command": "mkdir",
            "type": "shell",
            "args": [
                "-p",
                "build"
            ],
            "windows": {"options": {"shell": {"executable": "powershell.exe"
                    }
                },
                "args": [
                    "-Force",
                    "build"
                ],
            }
        },
        {// 在build文件夾中調(diào)用cmake進(jìn)行項(xiàng)目配置
            // 除windows系統(tǒng)外執(zhí)行的命令為`cmake -DCMAKE_BUILD_TYPE=../`
            // windows系統(tǒng)是在visual stuido的環(huán)境中執(zhí)行命令`cmake -DCMAKE_BUILD_TYPE=../ -G "CodeBlocks - NMake Makefiles"`
            "label": "cmake",
            "type": "shell",
            "command": "cmake",
            "args": [
                "-DCMAKE_BUILD_TYPE=${input:CMAKE_BUILD_TYPE}",
                "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", // 生成compile_commands.json 供c/c++擴(kuò)展提示使用
                "../"
            ],
            "options": {"cwd": "${workspaceFolder}/build",
            },
            "windows": {"args": [
                    "-DCMAKE_BUILD_TYPE=${input:CMAKE_BUILD_TYPE}",
                    "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
                    "../",
                    "-G",
                    "\"CodeBlocks - NMake Makefiles\""
                ],
                "options": {"shell": {// 需要根據(jù)安裝的vs版本調(diào)用vs工具命令提示符,根據(jù)自己的計(jì)算機(jī)上的路徑進(jìn)行修改
                        "executable": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat",
                        "args": [
                            "${input:PLATFORM}", //指定平臺(tái)
                            "-vcvars_ver=${input:vcvars_ver}", //指定vc環(huán)境版本
                            "&&"
                        ]
                    }
                },
            },
            "dependsOn": [
                "build_dir" // 在task `build_dir` 后執(zhí)行該task
            ]
        },
        {// 在build文件夾中調(diào)用cmake編譯構(gòu)建debug程序
            // 執(zhí)行的命令為`cmake --build ./ --target all --`
            //  windows系統(tǒng)如上需要在visual stuido的環(huán)境中執(zhí)行命令
            "label": "build",
            "group": "build",
            "type": "shell",
            "command": "cmake",
            "args": [
                "--build",
                "./",
                "--target",
                "all",
                "--"
            ],
            "options": {"cwd": "${workspaceFolder}/build",
            },
            "problemMatcher": "$gcc",
            "windows": {"options": {"shell": {"executable": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat",
                        "args": [
                            "${input:PLATFORM}",
                            "-vcvars_ver=${input:vcvars_ver}",
                            "&&"
                        ]
                    }
                },
                "problemMatcher": "$msCompile"
            },
            "dependsOn": [
                "cmake" // 在task `cmake` 后執(zhí)行該task
            ]
        },
        {"label": "Open Terminal",
            "type": "shell",
            "command": "osascript -e 'tell application \"Terminal\"\ndo script \"echo hello\"\nend tell'",
            "problemMatcher": []
        }
    ],
    "inputs": [
        {"id": "CMAKE_BUILD_TYPE",
            "type": "pickString",
            "description": "指定 CMAKE_BUILD_TYPE 的值",
            "options": [
                "Debug",
                "Release",
                "RelWithDebInfo",
                "MinSizeRel",
            ],
            "default": "Debug"
        },
        {"id": "PLATFORM",
            "type": "pickString",
            "description": "指定 PLATFORM 的值",
            "options": [
                "x86",
                "amd64",
                "arm",
                "x86_arm",
                "x86_amd64",
                "amd64_x86",
                "amd64_arm",
            ],
            "default": "amd64"
        },
        {"id": "vcvars_ver",
            "type": "pickString",
            "description": "指定 vcvars_ver 的值",
            "options": [
                "14.2", // 2019
                "14.1", // 2017
                "14.0", // 2015
            ],
            "default": "14.2"
        }
    ]
}

注意,如果是需要以Attach Debug方式啟動(dòng)的調(diào)試的話,運(yùn)行中的進(jìn)程在編譯的時(shí)候必須要加上-g以將符號(hào)表寫入到程序中,從而能夠debug程序,若使用CMake工具的話,需要指定使用Debug方式來構(gòu)建程序,而非MinSizeRel等其他構(gòu)建方式

// launch.json
{// Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {//名稱
            "name": "Launch Debug",
            //調(diào)試類型,除使用msvc進(jìn)行調(diào)試外,均為該類型
            "type": "cppdbg",
            "request": "launch",
            //指定C/C++程序位置
            "program": "${workspaceFolder}/bin/${input:executable}",
            //指定運(yùn)行參數(shù)
            "args": [
                "test.bin",
                "sorted.bin"
            ],
            "stopAtEntry": false,
            //指定工作目錄
            "cwd": "${workspaceFolder}",
            //在調(diào)試前會(huì)先調(diào)用build_debug這個(gè)task編譯構(gòu)建程序
            "preLaunchTask": "build",
            "environment": [],
            //macOS的特定配置
            "osx": {//指定使用lldb進(jìn)行調(diào)試
                "MIMode": "lldb",
                // 使用外部終端
                "externalConsole": true,
            },
            //linux的特定配置
            "linux": {//指定使用gdb調(diào)試
                "MIMode": "gdb",
                "setupCommands": [
                    {"description": "Enable pretty-printing for gdb",
                        "text": "-enable-pretty-printing",
                        "ignoreFailures": true
                    }
                ]
            },
            //windows的特定配置
            "windows": {//指定使用msvc進(jìn)行調(diào)試
                "type": "cppdbg",
                //指定C/C++程序位置
                "program": "${workspaceFolder}/build/${workspaceFolderBasename}.exe",
            }
        },
        {//名稱
            "name": "Attach Debug",
            //調(diào)試類型,除使用msvc進(jìn)行調(diào)試外,均為該類型
            "type": "cppdbg",
            "request": "attach",
            //指定C/C++程序位置
            "program": "${workspaceFolder}/bin/${input:executable}",
            //指定要attach的線程
            "processId":"${command:pickProcess}",
            "osx": {//指定使用lldb進(jìn)行調(diào)試
                "MIMode": "lldb",
                // 使用外部終端
                "externalConsole": true,
            },
            //linux的特定配置
            "linux": {//指定使用gdb調(diào)試
                "MIMode": "gdb",
                "setupCommands": [
                    {"description": "Enable pretty-printing for gdb",
                        "text": "-enable-pretty-printing",
                        "ignoreFailures": true
                    }
                ]
            },
            //windows的特定配置
            "windows": {//指定使用msvc進(jìn)行調(diào)試
                "type": "cppdbg",
                //指定C/C++程序位置
                "program": "${workspaceFolder}/build/${workspaceFolderBasename}.exe",
            }
        }
    ],
    "inputs": [
        {"id": "executable",
            "type": "pickString",
            "description": "可執(zhí)行文件的名稱",
            "default": "posrt",
            "options": [
                "psort"
            ]
        }
    ]
}

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧

名稱欄目:VSCode調(diào)試C/C++項(xiàng)目-創(chuàng)新互聯(lián)
轉(zhuǎn)載來源:http://vcdvsql.cn/article42/ddgihc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄網(wǎng)站導(dǎo)航品牌網(wǎng)站設(shè)計(jì)虛擬主機(jī)商城網(wǎng)站品牌網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司