linux下gcc編譯多個源文件、gdb的使用方法


一. gcc常用編譯命令選項

假設源程序文件名為test.c。

1. 無選項編譯鏈接
用法:#gcc test.c
作用:將test.c預處理、匯編、編譯並鏈接形成可執行文件。這里未指定輸出文件,默認輸出為a.out。

2. 選項 -o
用法:#gcc test.c -o test
作用:將test.c預處理、匯編、編譯並鏈接形成可執行文件test。-o選項用來指定輸出文件的文件名。

3. 選項 -E
用法:#gcc -E test.c -o test.i
作用:將test.c預處理輸出test.i文件。

4. 選項 -S
用法:#gcc -S test.i 
作用:將預處理輸出文件test.i匯編成test.s文件。

5. 選項 -c
用法:#gcc -c test.s
作用:將匯編輸出文件test.s編譯輸出test.o文件。

6. 無選項鏈接
用法:#gcc test.o -o test
作用:將編譯輸出文件test.o鏈接成最終可執行文件test。

7. 選項-O
用法:#gcc -O1 test.c -o test
作用:使用編譯優化級別1編譯程序。級別為1~3,級別越大優化效果越好,但編譯時間越長。

 

二. gcc多源文件的編譯方法

如果有多個源文件,基本上有兩種編譯方法:
[假設有兩個源文件為test.c和testfun.c]

1. 多個文件一起編譯
用法:#gcc testfun.c test.c -o test
作用:將testfun.c和test.c分別編譯后鏈接成test可執行文件。

2. 分別編譯各個源文件,之后對編譯后輸出的目標文件鏈接。
用法:
#gcc -c testfun.c //將testfun.c編譯成testfun.o
#gcc -c test.c //將test.c編譯成test.o
#gcc -o testfun.o test.o -o test //將testfun.o和test.o鏈接成test

以上兩種方法相比較,第一中方法編譯時需要所有文件重新編譯,而第二種方法可以只重新編譯修改的文件,未修改的文件不用重新編譯。

3. 如果要編譯的文件都在同一個目錄下,可以用通配符gcc *.c -o 來進行編譯。

你是否會問,如果是一個項目的話,可能會有上百個文件,這樣的編譯法,人不是要累死在電腦前嗎,或者等到你編譯成功了,豈不是頭發都白了,呵呵,所以我們要把上述的編譯過程寫進以下一個文本文件中:
Linux下稱之為makefile

#這里可以寫一些文件的說明
MyFirst: MyFirst.o hello.o
g++ MyFirst.o hello.o -o MyFirst
Hello.o:Hello.cpp
g++ -c Hello.cpp -o Hello.o
MyFirst.o:MyFirst.cpp
g++ -c MyFirst.cpp -o MyFirst.o

makefile 編寫規則:
(1)以“#”開始的行為注釋
(2)文件依賴關系為:
    target:components
    rule

存盤為MyFirst,在終端輸入:make MyFist

關於makefile的使用,可以參考:http://www.cnblogs.com/wang_yb/p/3990952.html

三、gdb常用命令:

[root@redhat home]#gdb 調試文件:啟動gdb

(gdb) l :(字母l)從第一行開始列出源碼

(gdb) break n :在第n行處設置斷點

(gdb) break func:在函數func()的入口處設置斷點

(gdb) info break: 查看斷點信息

(gdb) r:運行程序

(gdb) n:單步執行

(gdb) c:繼續運行

(gdb) p 變量 :打印變量的值

(gdb) bt:查看函數堆棧

(gdb) finish:退出函數

(gdb) shell 命令行:執行shell命令行

(gdb) set args 參數:指定運行時的參數

(gdb) show args:查看設置好的參數

(gdb) show paths:查看程序運行路徑;

           set environment varname [=value] 設置環境變量。如:set env USER=hchen;

            show environment [varname] 查看環境變量;

(gdb) cd 相當於shell的cd;

(gdb)pwd :顯示當前所在目錄

(gdb)info program: 來查看程序的是否在運行,進程號,被暫停的原因。

(gdb)clear 行號n:清除第n行的斷點

(gdb)delete 斷點號n:刪除第n個斷點

(gdb)disable 斷點號n:暫停第n個斷點

(gdb)enable 斷點號n:開啟第n個斷點

(gdb)step:單步調試如果有函數調用,則進入函數;與命令n不同,n是不進入調用的函數的

 

list :簡記為 l ,其作用就是列出程序的源代碼,默認每次顯示10行。
list 行號:將顯示當前文件以“行號”為中心的前后10行代碼,如:list 12
list 函數名:將顯示“函數名”所在函數的源代碼,如:list main
list :不帶參數,將接着上一次 list 命令的,輸出下邊的內容。 注意 :如果運行list 命令得到類似如下的打印,那是因為在編譯程序時沒有加入 -g 選項:
(gdb) list
1       ../sysdeps/i386/elf/start.S: No such file or directory.
        in ../sysdeps/i386/elf/start.S

run:簡記為 r ,其作用是運行程序,當遇到斷點后,程序會在斷點處停止運行,等待用戶輸入下一步的命令。
回車:重復上一條命令。
set args:設置運行程序時的命令行參數,如:set args 33 55
show args:顯示命令行參數
continue:簡訊為 c ,其作用是繼續運行被斷點中斷的程序。
break:為程序設置斷點。
break 行號:在當前文件的“行號”處設置斷點,如:break  33
break 函數名:在用戶定義的函數“函數名”處設置斷點,如:break cb_button
info breakpoints:顯示當前程序的斷點設置情況
disable breakpoints Num:關閉斷點“Num”,使其無效,其中“Num”為 info breakpoints 中顯示的對應值
enable breakpoints Num:打開斷點“Num”,使其重新生效
step:簡記為 s ,單步跟蹤程序,當遇到函數調用時,則進入此函數體(一般只進入用戶自定義函數)。
next:簡記為 n,單步跟蹤程序,當遇到函數調用時,也不進入此函數體;此命令同 step 的主要區別是,step 遇到用戶自定義的函數,將步進到函數中去運行,而 next 則直接調用函數,不會進入到函數體內。
until:當你厭倦了在一個循環體內單步跟蹤時,這個命令可以運行程序直到退出循環體。
finish: 運行程序,直到當前函數完成返回,並打印函數返回時的堆棧地址和返回值及參數值等信息。
stepi或nexti:單步跟蹤一些機器指令。
print 表達式:簡記為 p ,其中“表達式”可以是任何當前正在被測試程序的有效表達式,比如當前正在調試C語言的程序,那么“表達式”可以是任何C語言的有效表達式,包括數字,變量甚至是函數調用。
print a:將顯示整數 a 的值
print ++a:將把 a 中的值加1,並顯示出來
print name:將顯示字符串 name 的值
print gdb_test(22):將以整數22作為參數調用 gdb_test() 函數
print gdb_test(a):將以變量 a 作為參數調用 gdb_test() 函數
bt:顯示當前程序的函數調用堆棧。

display 表達式:在單步運行時將非常有用,使用display命令設置一個表達式后,它將在每次單步進行指令后,緊接着輸出被設置的表達式及值。如: display a
watch 表達式:設置一個監視點,一旦被監視的“表達式”的值改變,gdb將強行終止正在被調試的程序。如: watch a
kill:將強行終止當前正在調試的程序
help 命令:help 命令將顯示“命令”的常用幫助信息
call 函數(參數):調用“函數”,並傳遞“參數”,如:call  gdb_test(55)
layout:用於分割窗口,可以一邊查看代碼,一邊測試:
layout src:顯示源代碼窗口
layout asm:顯示反匯編窗口
layout regs:顯示源代碼/反匯編和CPU寄存器窗口
layout split:顯示源代碼和反匯編窗口
Ctrl + L:刷新窗口
quit:簡記為 q ,退出gdb

四、gdb 學習小例:

#include <stdio.h>

int add_range(int low, int high)
{
    int i, sum;
    for (i = low; i <= high; i++)
        sum = sum + i;
    return sum;
}

int main(void)
{
    int result[100];
    result[0] = add_range(1, 10);
    result[1] = add_range(1, 100);
    printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
    return 0;
}

add_range函數從low加到high,在main函數中首先從1加到10,把結果保存下來,然后從1加到100,再把結果保存下來,最后打印的兩個結果是:

result[0]=55
result[1]=5105

第一個結果正確, 第二個結果顯然不正確,在小學我們就聽說過高斯小時候的故事,從1加到100應該是5050。一段代碼,第一次運行結果是對的,第二次運行卻不對,這是很 常見的一類錯誤現象,這種情況不應該懷疑代碼而應該懷疑數據,因為第一次和第二次運行的都是同一段代碼,如果代碼是錯的,那為什么第一次的結果能對呢?然 而第一次和第二次運行時相關的數據卻有可能不同,錯誤的數據會導致錯誤的結果。在動手調試之前,讀者先試試只看代碼能不能看出錯誤原因,只要前面幾章學得 扎實就應該能看出來。

在編譯時要加上-g選項,生成的可執行文件才能用gdb進行源碼級調試:

$ gcc -g main.c -o main
$ gdb main
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb) 

-g選項的作用是在可執行文件中加入源代碼的信息,比如可執行文件中第幾條機器指令對應源代碼的第幾行,但並不是把整個源文件嵌入到可執行文件中,所以在調試時必須保證gdb能找到源文件。gdb提供一個類似Shell的命令行環境,上面的(gdb)就是提示符,在這個提示符下輸入help可以查看命令的類別:

(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.

也可以進一步查看某一類別中有哪些命令,例如查看files類別下有哪些命令可用:

(gdb) help files
Specifying and examining files.

List of commands:

add-shared-symbol-files -- Load the symbols from shared objects in the dynamic linker's link map
add-symbol-file -- Load symbols from FILE
add-symbol-file-from-memory -- Load the symbols out of memory from a dynamically loaded object file
cd -- Set working directory to DIR for debugger and program being debugged
core-file -- Use FILE as core dump for examining memory and registers
directory -- Add directory DIR to beginning of search path for source files
edit -- Edit specified file or function
exec-file -- Use FILE as program for getting contents of pure memory
file -- Use FILE as program to be debugged
forward-search -- Search for regular expression (see regex(3)) from last line listed
generate-core-file -- Save a core file with the current state of the debugged process
list -- List specified function or line
...

現在試試用list命令從第一行開始列出源代碼:

(gdb) list 1
1	#include <stdio.h>
2	
3	int add_range(int low, int high)
4	{
5		int i, sum;
6		for (i = low; i <= high; i++)
7			sum = sum + i;
8		return sum;
9	}
10

一次只列10行,如果要從第11行開始繼續列源代碼可以輸入

(gdb) list

也可以什么都不輸直接敲回車,gdb提供了一個很方便的功能,在提示符下直接敲回車表示重復上一條命令。

(gdb) (直接回車)
11	int main(void)
12	{
13		int result[100];
14		result[0] = add_range(1, 10);
15		result[1] = add_range(1, 100);
16		printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
17		return 0;
18

gdb的很多常用命令有簡寫形式,例如list命令可以寫成l,要列一個函數的源代碼也可以用函數名做參數:

(gdb) l add_range
1	#include <stdio.h>
2	
3	int add_range(int low, int high)
4	{
5		int i, sum;
6		for (i = low; i <= high; i++)
7			sum = sum + i;
8		return sum;
9	}
10

現在退出gdb的環境:

(gdb) quit

我們做一個實驗,把源代碼改名或移到別處再用gdb調試,這樣就列不出源代碼了:

$ mv main.c mian.c
$ gdb main
...
(gdb) l
5	main.c: No such file or directory.
	in main.c

可見gcc-g選項並不是把源代碼嵌入到可執行文件中的,在調試時也需要源文件。現在把源代碼恢復原樣,我們繼續調試。首先用start命令開始執行程序:

$ gdb main
...
(gdb) start
Breakpoint 1 at 0x80483ad: file main.c, line 14.
Starting program: /home/akaedu/main 
main () at main.c:14
14		result[0] = add_range(1, 10);
(gdb)

gdb停在main函數中變量定義之后的第一條語句處等待我們發命令,gdb列出的這條語句是即將執行的下一條語句。我們可以用next命令(簡寫為n)控制這些語句一條一條地執行:

(gdb) n
15		result[1] = add_range(1, 100);
(gdb) (直接回車)
16		printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
(gdb) (直接回車)
result[0]=55
result[1]=5105
17		return 0;

n命令依次執行兩行賦值語句和一行打印語句,在執行打印語句時結果立刻打出來了,然后停在return語句之前等待我們發命令。雖然我們完全控制了程序的執行,但仍然看不出哪里錯了,因為錯誤不在main函數中而在add_range函數中,現在用start命令重新來過,這次用step命令(簡寫為s)鑽進add_range函數中去跟蹤執行:

(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Breakpoint 2 at 0x80483ad: file main.c, line 14.
Starting program: /home/akaedu/main 
main () at main.c:14
14		result[0] = add_range(1, 10);
(gdb) s
add_range (low=1, high=10) at main.c:6
6		for (i = low; i <= high; i++)

這次停在了add_range函數中變量定義之后的第一條語句處。在函數中有幾種查看狀態的辦法,backtrace命令(簡寫為bt)可以查看函數調用的棧幀:

(gdb) bt
#0  add_range (low=1, high=10) at main.c:6
#1  0x080483c1 in main () at main.c:14

可見當前的add_range函數是被main函數調用的,main傳進來的參數是low=1, high=10main函數的棧幀編號為1,add_range的棧幀編號為0。現在可以用info命令(簡寫為i)查看add_range函數局部變量的值:

(gdb) i locals
i = 0
sum = 0

如果想查看main函數當前局部變量的值也可以做到,先用frame命令(簡寫為f)選擇1號棧幀然后再查看局部變量:

(gdb) f 1
#1  0x080483c1 in main () at main.c:14
14		result[0] = add_range(1, 10);
(gdb) i locals 
result = {0, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480, 
...
  -1208623680}

注意到result數組中有很多元素具有雜亂無章的值,我們知道未經初始化的局部變量具有不確定的值。到目前為止一切正常。用sn往下走幾步,然后用print命令(簡寫為p)打印出變量sum的值:

(gdb) s
7			sum = sum + i;
(gdb) (直接回車)
6		for (i = low; i <= high; i++)
(gdb) (直接回車)
7			sum = sum + i;
(gdb) (直接回車)
6		for (i = low; i <= high; i++)
(gdb) p sum
$1 = 3

第一次循環i是1,第二次循環i是2,加起來是3,沒錯。這里的$1表示gdb保存着這些中間結果,$后面的編號會自動增長,在命令中可以用$1$2$3等編號代替相應的值。由於我們本來就知道第一次調用的結果是正確的,再往下跟也沒意義了,可以用finish命令讓程序一直運行到從當前函數返回為止:

(gdb) finish
Run till exit from #0  add_range (low=1, high=10) at main.c:6
0x080483c1 in main () at main.c:14
14		result[0] = add_range(1, 10);
Value returned is $2 = 55

返回值是55,當前正准備執行賦值操作,用s命令賦值,然后查看result數組:

(gdb) s
15		result[1] = add_range(1, 100);
(gdb) p result
$3 = {55, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480, 
...
  -1208623680}

第一個值55確實賦給了result數組的第0個元素。下面用s命令進入第二次add_range調用,進入之后首先查看參數和局部變量:

(gdb) s
add_range (low=1, high=100) at main.c:6
6		for (i = low; i <= high; i++)
(gdb) bt
#0  add_range (low=1, high=100) at main.c:6
#1  0x080483db in main () at main.c:15
(gdb) i locals 
i = 11
sum = 55

由於局部變量isum沒初始化,所以具有不確定的值,又由於兩次調用是挨着的,isum正好取了上次調用時的值。i的初值不是0倒沒關系,在for循環中會賦值為0的,但sum如果初值不是0,累加得到的結果就錯了。好了,我們已經找到錯誤原因,可以退出gdb修改源代碼了。如果我們不想浪費這次調試機會,可以在gdb中馬上把sum的初值改為0繼續運行,看看這一處改了之后還有沒有別的Bug:

(gdb) set var sum=0
(gdb) finish
Run till exit from #0  add_range (low=1, high=100) at main.c:6
0x080483db in main () at main.c:15
15		result[1] = add_range(1, 100);
Value returned is $4 = 5050
(gdb) n
16		printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
(gdb) (直接回車)
result[0]=55
result[1]=5050
17		return 0;

這樣結果就對了。修改變量的值除了用set命令之外也可以用print命令,因為print命令后面跟的是表達式,而我們知道賦值和函數調用也都是表達式,所以也可以用print命令修改變量的值或者調用函數:

 (gdb) p result[2]=33
 $5 = 33
 (gdb) p printf("result[2]=%d\n", result[2])
 result[2]=33
 $6 = 13

五、多線程調試
1. 多線程調試,最重要的幾個命令:
info threads                        查看當前進程的線程。
                                          GDB會為每個線程分配一個ID, 后面操作線程的時候會用到這個ID.
                                          前面有*的是當前調試的線程.
thread <ID>                      切換調試的線程為指定ID的線程。
break file.c:100 thread all    在file.c文件第100行處為所有經過這里的線程設置斷點。
set scheduler-locking off|on|step    
      在使用step或者continue命令調試當前被調試線程的時候,其他線程也是同時執行的,
      怎么只讓被調試程序執行呢?
      通過這個命令就可以實現這個需求。
         off      不鎖定任何線程,也就是所有線程都執行,這是默認值。
         on       只有當前被調試程序會執行。
         step     在單步的時候,除了next過一個函數的情況
                  (熟悉情況的人可能知道,這其實是一個設置斷點然后continue的行為)以外,
                  只有當前線程會執行。
thread apply ID1 ID2 command        讓一個或者多個線程執行GDB命令command
thread apply all command            讓所有被調試線程執行GDB命令command。

2. 使用示例:
線程產生通知:在產生新的線程時, gdb會給出提示信息
(gdb) r
Starting program: /root/thread 
[New Thread 1073951360 (LWP 12900)] 
[New Thread 1082342592 (LWP 12907)]---以下三個為新產生的線程
[New Thread 1090731072 (LWP 12908)]
[New Thread 1099119552 (LWP 12909)]

查看線程:使用info threads可以查看運行的線程。
(gdb) info threads
  4 Thread 1099119552 (LWP 12940)   0xffffe002 in ?? ()
  3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
  2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
* 1 Thread 1073951360 (LWP 12931)   main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)
注意,行首為gdb分配的線程ID號,對線程進行切換時,使用該ID號碼。
另外,行首的星號標識了當前活動的線程
切換線程:
使用 thread THREADNUMBER 進行切換,THREADNUMBER 為上文提到的線程ID號。
下例顯示將活動線程從 1 切換至 4。
(gdb) info threads
   4 Thread 1099119552 (LWP 12940)   0xffffe002 in ?? ()
   3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
   2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
* 1 Thread 1073951360 (LWP 12931)   main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb) thread 4
[Switching to thread 4 (Thread 1099119552 (LWP 12940))]#0   0xffffe002 in ?? ()
(gdb) info threads
* 4 Thread 1099119552 (LWP 12940)   0xffffe002 in ?? ()
   3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
   2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
   1 Thread 1073951360 (LWP 12931)   main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)
以上即為使用gdb提供的對多線程進行調試的一些基本命令。
另外,gdb也提供對線程的斷點設置以及對指定或所有線程發布命令的命令

六、調試宏
在GDB下, 我們無法print宏定義,因為宏是預編譯的。
但是我們還是有辦法來調試宏,這個需要GCC的配合。
在GCC編譯程序的時候,加上
  -ggdb3   參數,這樣,你就可以調試宏了。

另外,你可以使用下述的GDB的宏調試命令 來查看相關的宏。
info macro   查看這個宏在哪些文件里被引用了,以及宏定義是什么樣的。
macro         查看宏展開的樣子。

、源文件
GDB時,提示找不到源文件。
需要做下面的檢查:
編譯程序員是否加上了 -g參數 以包含debug信息。
路徑是否設置正確了。
使用GDB的directory命令來設置源文件的目錄。

下面給一個調試/bin/ls的示例(ubuntu下)
$ apt-get source coreutils
$ sudo apt-get install coreutils-dbgsym
$ gdb /bin/ls
GNU gdb (GDB) 7.1-ubuntu
(gdb) list main
1192    ls.c: No such file or directory.
in ls.c
(gdb) directory ~/src/coreutils-7.4/src/
Source directories searched: /home/hchen/src/coreutils-7.4:$cdir:$cwd
(gdb) list main
1192        }
1193    }
1194
1195    int
1196    main (int argc, char **argv)
1197    {
1198      int i;
1199      struct pending *thispend;
1200      int n_files;
1201

八、條件斷點
條件斷點是語法是:
  break  [where] if [condition]
這種斷點真是非常管用。
尤其是在一個循環或遞歸中,或是要監視某個變量。
注意,這個設置是在GDB中的,只不過每經過那個斷點時GDB會幫你檢查一下條件是否滿足。

九、命令行參數
有時候,我們需要調試的程序需要有命令行參數, 有三種方法:
gdb命令行的 -args 參數
gdb環境中   set args命令。
gdb環境中   run 參數

十、gdb的變量
有時候,在調試程序時,我們不單單只是查看運行時的變量,
我們還可以直接設置程序中的變量,以模擬一些很難在測試中出現的情況,比較一些出錯,
或是switch的分支語句。使用set命令可以修改程序中的變量。
另外,你知道gdb中也可以有變量嗎?
就像shell一樣,gdb中的變量以$開頭,比如你想打印一個數組中的個個元素,你可以這樣:
(gdb) set $i = 0
(gdb) p a[$i++]
...  #然后就一路回車下去了
當然,這里只是給一個示例,表示程序的變量和gdb的變量是可以交互的。

十一、x命令
也許,你很喜歡用p命令。
所以,當你不知道變量名的時候,你可能會手足無措,因為p命令總是需要一個變量名的。
x命令是用來查看內存的,在gdb中 “help x” 你可以查看其幫助。
x/x 以十六進制輸出
x/d 以十進制輸出
x/c 以單字符輸出
x/i  反匯編 – 通常,我們會使用 x/10i $ip-20 來查看當前的匯編($ip是指令寄存器)
x/s 以字符串輸出

十二、command命令
如何自動化調試。
這里向大家介紹command命令,簡單的理解一下,其就是把一組gdb的命令打包,有點像字處理軟件的“宏”。
下面是一個示例:
(gdb) break func
Breakpoint 1 at 0x3475678: file test.c, line 12.
(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>print arg1
>print arg2
>print arg3
>end
(gdb)
當我們的斷點到達時,自動執行command中的三個命令,把func的三個參數值打出來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM