這里寫自定義目錄標題
- 一、`sizeof` 計算(32位環境)
- 二、簡答題
- 三、數據存儲區域與可修改性
- 四、字符串比較輸出及原因
- 五、數組指針運算輸出
- 六、字符串倒序代碼錯誤排查
- 七、下面程序可以把1維數組轉為2維數組,然后調用 printArr2D 打印出數組內容,請補全調用 printArr2D部分的代碼(可以增加變量)。(10分)
- 八、使用C語言實現void memecpy(void* pdest,void* psrc,size_t nsrc)
一、sizeof
計算(32位環境)
1.1 sizeof(str)
(char str[] = "Hello";
)
- 字符串
"Hello"
實際存儲為'H' 'e' 'l' 'l' 'o' '\0'
,共 6 字節,sizeof
計算數組總字節數,結果為6
。
1.2 sizeof(p)
(char *p = str;
)
- 32位系統中,指針變量占 4 字節(存儲內存地址),結果為
4
。
1.3 sizeof(n)
(long n = 10;
)
- 32位系統里,
long
類型占 4 字節,結果為4
。
1.4 sizeof(str)
(void foo(char str[100])
)
- 數組作為函數參數時,退化為指針,32位下指針占4字節,結果為
4
(注:char str[100]
形式僅語法兼容,實際是指針)。
1.5 sizeof(p)
(void *p = malloc(100);
)
- 指針變量
p
存地址,32位下占 4 字節,結果為4
(malloc
分配堆內存,但指針本身大小由系統位數決定)。
我的錯誤點:
sizeof(str)
(char str[] = "Hello";
)
我的答案:4
我給的原因:字符數組的數組名難道不是指針嗎?指針就是4啊?
豆包給我解答
你的理解存在一個關鍵誤區:數組名本身不是指針,只有在特定上下文中才會“退化”為指針。讓我用最直觀的方式解釋清楚:
①、直接結論
對于 char str[] = "Hello";
:
sizeof(str)
的結果是6
(字符串長度5 + 結尾的'\0'
,共6字節)。- 數組名
str
不是指針,它代表整個數組的內存區域。
②、為什么容易誤以為數組名是指針?
因為在大多數表達式中,數組名會被隱式轉換為指向首元素的指針(這叫“退化”)。比如:
char* ptr = str; // 這里 str 退化為指針,等價于 ptr = &str[0];
但注意,這種退化不是無條件的!有兩個例外:
sizeof(數組名)
:返回數組總大小,而非指針大小。&數組名
:返回數組的地址(類型是char(*)[N]
,而非char*
)。
③、用例子驗證
看這段代碼:
#include <stdio.h>int main() {char str[] = "Hello";char* ptr = str; // str 退化為指針printf("sizeof(str) = %zu\n", sizeof(str)); // 輸出 6printf("sizeof(ptr) = %zu\n", sizeof(ptr)); // 輸出 4/8(指針大小)printf("str 的地址: %p\n", (void*)str); // 首元素地址printf("&str 的地址: %p\n", (void*)&str); // 數組的地址(數值上與首元素相同,但類型不同)return 0;
}
輸出結果(32位系統):
sizeof(str) = 6
sizeof(ptr) = 4
str 的地址: 0x7ffd5a0b8d80
&str 的地址: 0x7ffd5a0b8d80
- 雖然
str
和&str
的數值相同,但類型不同:str
是char[6]
類型(數組)。&str
是char(*)[6]
類型(指向數組的指針)。
④、對比:真正的指針
如果直接定義指針:
char* ptr = "Hello"; // 指針指向常量區的字符串
此時:
sizeof(ptr)
永遠是 4/8字節(取決于系統是32位還是64位)。- 字符串
"Hello"
存儲在常量區,ptr
只是存儲它的地址。
⑤、一句話總結
數組名 ≠ 指針
數組名在內存中直接表示整個數組的內存區域,而指針是一個存儲地址的變量。sizeof
作用于數組名時,返回的是數組實際占用的內存大小,而非指針的大小。
二、簡答題
const
的用途(至少兩種)
- 修飾變量:定義只讀變量,編譯期限制修改(如
const int a = 10;
,a
不可被賦值改變 )。 - 修飾函數參數:保護傳入參數,防止函數內意外修改(如
void func(const int x)
,確保x
在函數內只讀 )。 - 修飾函數返回值:限制返回值不可被直接修改(如
const int func()
,返回值賦值給普通變量不影響,但賦值給const
變量可增強約束 )。
- C++ 調用 C 編譯函數加
extern "C"
的原因
- 解決命名沖突:C++ 支持函數重載,編譯會給函數名加額外修飾(如
func(int)
變成_func_int
);C 編譯的函數名無修飾。加extern "C"
讓 C++ 按 C 規則查找函數名,保證調用匹配。
我的回答:可以讓C語言調用C++的接口不夠完善!
八股文的回答:
在C++中,"extern”關鍵字有兩個主要作用:
①聲明變量的外部鏈接:
? 當一個變量在一個文件中聲明,但在另一個文件中定義時,我們可以使用"extern”關鍵字來告知編譯器該變量在其他地方定義,避免編譯器的編譯錯誤。
②extern"C":
? 在導出C++函數符號時,通過extern"C",可以保證導出的符號為C符號,而不是C++的符號(namemangling),這樣可以更好的做兼容。比如Ilvm編譯器導出的庫,通過C符號可以做到MSVC編譯器也可以正常鏈接使用。
int (*s[10])(int)
表示的含義
- 這是一個函數指針數組:
s
是數組,包含 10 個元素;- 每個元素是函數指針,指向的函數接收
int
類型參數、返回int
類型結果。
三、數據存儲區域與可修改性
char s[] = "abc";
"abc"
存于靜態初始化區域/全局/靜態存儲區(數組s
是局部變量,但字符串字面量編譯時放靜態區,運行時數組s
會拷貝字面量內容到棧上,不過嚴格說字面量源頭在靜態區 )。
char *p1 = "123456";
"123456\0"
存于常量區(字符串字面量默認放常量區,p1
是指針,存于棧,指向常量區內容 )。
char *p2 = (char*)malloc(20);
p2
指向的內存由malloc
分配,存于堆區。
- 可修改性判斷
s[]
:數組s
在棧上,內容可修改(如s[0] = 'A';
合法 )。p1
:指向常量區,內容不可修改(p1[0] = 'A';
會觸發未定義行為,常量區只讀 )。p2
:指向堆區,內容可修改(如p2[0] = 'A';
合法,堆內存可讀寫 )。
我的錯誤:
char s[] = "abc";
我的答案:"abc"
存于 “常量區”
豆包回答我的問題:
這里需要區分字符串字面量本身的存儲和數組 s
的存儲,可以從編譯、運行階段拆分理解:
①字符串字面量 "abc"
的本質
-
編譯時:
字符串字面量"abc"
是程序的“靜態數據”,編譯器會把它放到只讀數據段(靜態區的一種)。不管你用不用char s[] = "abc";
,只要代碼里寫了"abc"
,它就會被存到靜態區,目的是讓多個相同字面量共享內存(比如char* p = "abc";
也會指向這里)。 -
運行時:
當執行char s[] = "abc";
時,程序會把靜態區里的"abc"
(包括末尾'\0'
)拷貝一份,放到棧上給數組s
使用。所以,字面量本身的“源頭”在靜態區,但數組
s
是棧上的拷貝,二者是“內容相同、存儲區不同”的關系。
② 為什么說數組 s
不在靜態區?
-
靜態區的特點:數據編譯時分配,程序結束才釋放,且全局可見(比如全局數組、
static
變量)。 -
數組
s
的特點:- 它是局部變量(假設在函數內定義),運行時在棧上分配內存,函數結束就釋放。
- 它的內容是靜態區字面量的“拷貝”,不是字面量本身。
因此,數組
s
存儲在棧上,而它的“數據源”(字面量"abc"
)在靜態區。
③、對比 char* p = "abc";
加深理解
-
char* p = "abc";
:
p
是指針(存在棧/全局區,看定義位置),它直接指向靜態區的"abc"
(沒有拷貝)。此時,靜態區的"abc"
是只讀的(修改p[0]
會觸發未定義行為)。 -
char s[] = "abc";
:
s
是數組(存在棧上),它拷貝靜態區的"abc"
到棧,內容可修改(s[0] = 'A';
合法)。
④、一句話總結
"abc"
作為字符串字面量,源頭在靜態區;但 char s[] = "abc";
是把字面量“復制”到棧上的數組,所以數組 s
存儲在棧,而非靜態區。核心區別是“直接引用靜態區”還是“拷貝到棧” 。
你的理解基本正確,但需要更精確地區分字符串字面量的存儲區域和變量(指針/數組)的存儲區域,以及術語的統一性(“常量區”和“靜態區”在很多場景下是重疊的概念)。下面詳細拆解:
⑤、關于 char* p1 = "123456";
- 字符串字面量
"123456"
的存儲:
字符串字面量"123456"
本質是常量字符串,編譯時會被放入只讀數據段(屬于靜態區/常量區)。- 它的完整內容是
"123456\0"
(編譯器自動添加結束符'\0'
),存儲在靜態區的只讀區域,不可修改(強行修改會觸發未定義行為,比如程序崩潰)。
- 它的完整內容是
⑥、關于 char s[] = "abc";
- 字符串字面量
"abc"
的存儲:
和"123456"
一樣,字符串字面量"abc"
本身在編譯時也存于靜態區(常量區),完整內容是"abc\0"
。
⑦、關鍵術語統一:靜態區 vs 常量區
- 靜態區:是程序內存布局的一個大分類,包含全局變量、靜態變量、常量字符串字面量等,特點是編譯時分配內存,程序結束后釋放。
- 常量區(只讀數據段):是靜態區的一部分,專門存儲不可修改的常量(如字符串字面量、
const
修飾的全局變量等)。
因此,“字符串字面量存在靜態區”和“存在常量區”并不矛盾——常量區屬于靜態區的子集,字符串字面量是靜態區中只讀的那部分。
⑧、總結表格
代碼 | 字符串字面量存儲位置 | 變量(指針/數組)存儲位置 | 能否修改內容? |
---|---|---|---|
char* p1 = "123"; | 靜態區(常量區,"123\0" ) | p1 本身在棧/靜態區(看定義) | 不能(字面量只讀) |
char s[] = "abc"; | 靜態區(常量區,"abc\0" ) | s 本身在棧/靜態區(看定義) | 能(拷貝的內容可改) |
簡單說:所有字符串字面量的“源頭”都在靜態區的常量區,但數組會拷貝字面量內容到自己的存儲區(棧或靜態區),而指針直接指向字面量本身。
以下是題目詳細解答,按題目模塊拆分說明:
四、字符串比較輸出及原因
代碼:
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";cout << (str1 == str2) << endl;
cout << (str3 == str4) << endl;
cout << (str5 == str6) << endl;
cout << (str7 == str8) << endl;
輸出結果:
0
0
1
1
原因:
str1
、str2
、str3
、str4
是數組,每個數組獨立分配內存(棧上),即使內容相同,地址不同,==
比較地址,結果為0
(假)。str5
、str6
、str7
、str8
是指針,指向字符串字面量(存常量區,相同字面量共享地址 ),所以str5
和str6
、str7
和str8
地址相同,==
結果為1
(真)。
我的答案: 0 0 0 0
我的原因: 不都是在棧區申請了的臨時變量指針嗎?
仔細思考: 指針說白了就是用來存儲地址的,說白了str5 str6 和str7 str8都指向的是同一塊地址,自然而然值相同,對于str1 str2 str3 str4 來說 char str1[] = “abc”; 存在棧區的str1 會把靜態常量區的“abc”拷貝到棧區?
豆包給我的回答:
詳細分析:
str1
和str2
是數組(棧上獨立內存)
char str1[] = "abc";
和char str2[] = "abc";
分別在棧上創建兩個獨立的數組。- 雖然它們的內容都是
"abc\0"
,但數組名代表數組首地址,而兩個數組在內存中的位置不同,因此str1
和str2
的地址不同。 str1 == str2
比較的是地址,結果為0
(假)。
str3
和str4
同理(const
不影響存儲位置)
const
僅限制對數組內容的修改,但數組本身仍在棧上分配獨立內存。- 因此
str3
和str4
的地址不同,str3 == str4
結果為0
。
str5
和str6
是指針(指向靜態區的同一字符串)
const char *str5 = "abc";
和str6
都指向靜態區的字符串字面量"abc\0"
。- 編譯器通常會將相同的字符串字面量合并為一處存儲,因此
str5
和str6
指向同一塊內存地址。 str5 == str6
比較的是指針值(地址),結果為1
(真)。
str7
和str8
同理(即使沒有const
,仍指向靜態區)
- 雖然
char *str7 = "abc";
沒有顯式聲明const
,但字符串字面量"abc\0"
仍存于靜態區且不可修改。 str7
和str8
同樣指向靜態區的同一字符串,因此str7 == str8
結果為1
。
關鍵區別:
- 數組名(如
str1
、str2
)代表數組首地址,每個數組在內存中獨立分配,地址不同。 - 指針(如
str5
、str6
)存儲的是字符串字面量的地址,相同的字面量通常共享同一內存位置。
因此,數組比較結果為 0
,指針比較結果為 1
。
五、數組指針運算輸出
代碼:
main()
{int a[5]={1,2,3,4,5};int *ptr=(int *)(&a+1);printf("%d,%d",*(a+1),*(ptr-1));
}
輸出結果:2,5
解析:
*(a + 1)
:a
是數組首地址,a + 1
指向a[1]
,值為2
。&a
是數組指針(類型int (*)[5]
),&a + 1
跳過整個數組(偏移5 * sizeof(int)
字節 ),ptr
指向數組末尾后第一個位置;ptr - 1
回退到a[4]
,值為5
。
六、字符串倒序代碼錯誤排查
原代碼(功能:字符串倒序):
#include"string.h"
main()
{char* src = "hello,world";char* dest = NULL;int len = strlen(src);dest = (char*)malloc(len); char* d = dest;char* s = src[len]; while(len-- !=0) d++ = s--; printf("%s", dest); return 0;
}
錯誤及修正:
-
malloc
空間不足:- 原代碼
malloc(len)
未給字符串結束符'\0'
留空間,需改為malloc(len + 1)
。
- 原代碼
-
src[len]
語法錯誤:src
是指針,src[len]
是字符(越界,src
有效下標0~len-1
),應改為src + len - 1
(指向最后一個有效字符 )。
-
未處理字符串結束符:
- 倒序后需手動在
dest
末尾加'\0'
,否則輸出亂碼。
- 倒序后需手動在
-
main
函數標準寫法:- 建議顯式寫
int main()
,增強可讀性。
- 建議顯式寫
修正后代碼:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>int main() {char* src = "hello,world";char* dest = NULL;int len = strlen(src);// 1. 分配足夠空間(含 '\0')dest = (char*)malloc(len + 1); char* d = dest;// 2. 指向最后一個有效字符char* s = src + len - 1; while (len-- != 0) {*d++ = *s--; }// 3. 手動加結束符*d = '\0'; printf("%s", dest); free(dest); // 釋放堆內存(可選,若關注內存泄漏)return 0;
}
最終答案整理
4. 輸出依次:0
、0
、1
、1
(數組地址不同,常量區字符串指針地址相同 )。
5. 輸出:2,5
(指針運算定位數組元素 )。
6. 錯誤:malloc
空間不足、src[len]
語法錯、缺結束符、main
不標準;修正后可正確倒序輸出 dlrow,olleh
。
七、下面程序可以把1維數組轉為2維數組,然后調用 printArr2D 打印出數組內容,請補全調用 printArr2D部分的代碼(可以增加變量)。(10分)
要調用 printArr2D
,需要構造一個符合 int**
類型的參數。由于 arr2D
是二維數組,在 C 語言中,二維數組名可以轉換為指向指針的指針,但更直接的方式是構造一個指針數組,每個元素指向二維數組的一行。
以下是補全的代碼:
#include <stdio.h>
#define ROW 2
#define COL 3void printArr2D(int** arr2D, int row, int col) {int i, j;for (i = 0; i < row; i++) {for (j = 0; j < col; j++) {printf("%d ", arr2D[i][j]);}printf("\n");}
}int main(int argc, char *argv[]) {int arr1D[] = {1, 2, 3, 4, 5, 6};int arr2D[ROW][COL];int i, j;for (i = 0; i < ROW; i++) {for (j = 0; j < COL; j++) {arr2D[i][j] = arr1D[i * COL + j];}}// 構造一個指針數組,每個元素指向 arr2D 的一行int* rowPtrs[ROW];for (i = 0; i < ROW; i++) {rowPtrs[i] = arr2D[i];}// 調用 printArr2D,傳遞指針數組printArr2D(rowPtrs, ROW, COL);return 0;
}
關鍵步驟說明:
-
構造指針數組
rowPtrs
:
由于printArr2D
的參數是int**
(指向指針的指針),而二維數組arr2D
的行地址可以通過指針數組存儲。
rowPtrs[i] = arr2D[i];
讓每個指針元素指向arr2D
的一行。 -
調用
printArr2D
:
直接傳遞rowPtrs
(類型為int**
),匹配函數參數要求。
這樣就能正確打印二維數組的內容,輸出:
1 2 3
4 5 6
八、使用C語言實現void memecpy(void* pdest,void* psrc,size_t nsrc)
實現思路
memcpy
函數用于將一段內存區域的數據復制到另一個區域,需要處理以下關鍵點:
- 內存重疊問題:標準
memcpy
不保證處理重疊內存(需用memmove
),但題目要求實現memcpy
,因此忽略重疊。 - 字節級復制:使用
char*
指針逐字節復制,確保處理任意數據類型。 - 參數合法性檢查:檢查輸入指針是否為
NULL
,提高函數健壯性。
代碼實現
#include <stddef.h> // 包含 size_t 定義void* memcpy(void* pdest, const void* psrc, size_t nsrc) {// 1. 檢查參數合法性(標準庫通常不檢查,此處為增強健壯性)if (pdest == NULL || psrc == NULL) {return NULL;}// 2. 轉換為 char* 以逐字節操作char* dest = (char*)pdest;const char* src = (const char*)psrc;// 3. 逐字節復制 nsrc 字節for (size_t i = 0; i < nsrc; i++) {dest[i] = src[i];}// 4. 返回目標指針return pdest;
}
關鍵說明
-
指針類型轉換:
- 將
void*
轉換為char*
以逐字節復制,確保正確處理任意數據類型。 src
被聲明為const char*
,防止修改源數據。
- 將
-
內存重疊處理:
- 若源區域與目標區域重疊,結果未定義(如
src
在dest
前且部分重疊,可能覆蓋未復制的數據)。 - 若需處理重疊,應使用
memmove
(通過逆向復制避免覆蓋)。
- 若源區域與目標區域重疊,結果未定義(如
-
返回值:
- 返回
pdest
以便鏈式調用(如strcpy(dest, memcpy(dest, src, n))
)。
- 返回
-
性能優化:
- 實際標準庫實現可能使用更高效的方式(如按字長復制或 SIMD 指令),但此處采用簡潔的逐字節復制以保證通用性。
此實現符合標準 memcpy
的行為,適用于非重疊內存的復制場景。