这里写自定义目录标题
一、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)); // 输出 6
printf("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 3
void 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
的行为,适用于非重叠内存的复制场景。