某外企笔试总结——纯C语言

发布于:2025-07-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、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 字节,结果为 4malloc 分配堆内存,但指针本身大小由系统位数决定)。

我的错误点:
sizeof(str)char str[] = "Hello";
我的答案:4
我给的原因:字符数组的数组名难道不是指针吗?指针就是4啊?
豆包给我解答

你的理解存在一个关键误区:数组名本身不是指针,只有在特定上下文中才会“退化”为指针。让我用最直观的方式解释清楚:

①、直接结论
对于 char str[] = "Hello";

  • sizeof(str) 的结果是 6(字符串长度5 + 结尾的 '\0',共6字节)。
  • 数组名 str 不是指针,它代表整个数组的内存区域。

②、为什么容易误以为数组名是指针?
因为在大多数表达式中,数组名会被隐式转换为指向首元素的指针(这叫“退化”)。比如:

char* ptr = str;  // 这里 str 退化为指针,等价于 ptr = &str[0];

但注意,这种退化不是无条件的!有两个例外:

  1. sizeof(数组名):返回数组总大小,而非指针大小。
  2. &数组名:返回数组的地址(类型是 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 的数值相同,但类型不同:
    • strchar[6] 类型(数组)。
    • &strchar(*)[6] 类型(指向数组的指针)。

④、对比:真正的指针
如果直接定义指针:

char* ptr = "Hello";  // 指针指向常量区的字符串

此时:

  • sizeof(ptr) 永远是 4/8字节(取决于系统是32位还是64位)。
  • 字符串 "Hello" 存储在常量区,ptr 只是存储它的地址。

⑤、一句话总结
数组名 ≠ 指针
数组名在内存中直接表示整个数组的内存区域,而指针是一个存储地址的变量。sizeof 作用于数组名时,返回的是数组实际占用的内存大小,而非指针的大小。

二、简答题

在这里插入图片描述

  1. const 的用途(至少两种)
  • 修饰变量:定义只读变量,编译期限制修改(如 const int a = 10;a 不可被赋值改变 )。
  • 修饰函数参数:保护传入参数,防止函数内意外修改(如 void func(const int x),确保 x 在函数内只读 )。
  • 修饰函数返回值:限制返回值不可被直接修改(如 const int func(),返回值赋值给普通变量不影响,但赋值给 const 变量可增强约束 )。
  1. 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编译器也可以正常链接使用。

  1. int (*s[10])(int) 表示的含义
  • 这是一个函数指针数组
    • s 是数组,包含 10 个元素;
    • 每个元素是函数指针,指向的函数接收 int 类型参数、返回 int 类型结果。

三、数据存储区域与可修改性

在这里插入图片描述

  1. char s[] = "abc";
  • "abc" 存于静态初始化区域/全局/静态存储区(数组 s 是局部变量,但字符串字面量编译时放静态区,运行时数组 s 会拷贝字面量内容到栈上,不过严格说字面量源头在静态区 )。
  1. char *p1 = "123456";
  • "123456\0" 存于常量区(字符串字面量默认放常量区,p1 是指针,存于栈,指向常量区内容 )。
  1. char *p2 = (char*)malloc(20);
  • p2 指向的内存由 malloc 分配,存于堆区
  1. 可修改性判断
  • s[]:数组 s 在栈上,内容可修改(如 s[0] = 'A'; 合法 )。
  • p1:指向常量区,内容不可修改(p1[0] = 'A'; 会触发未定义行为,常量区只读 )。
  • p2:指向堆区,内容可修改(如 p2[0] = 'A'; 合法,堆内存可读写 )。

我的错误:
char s[] = "abc"; 我的答案:"abc" 存于 “常量区”
豆包回答我的问题:
这里需要区分字符串字面量本身的存储数组 s 的存储,可以从编译、运行阶段拆分理解:

①字符串字面量 "abc" 的本质

  1. 编译时
    字符串字面量 "abc" 是程序的“静态数据”,编译器会把它放到只读数据段(静态区的一种)。不管你用不用 char s[] = "abc";,只要代码里写了 "abc",它就会被存到静态区,目的是让多个相同字面量共享内存(比如 char* p = "abc"; 也会指向这里)。

  2. 运行时
    当执行 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";

  1. 字符串字面量 "123456" 的存储
    字符串字面量 "123456" 本质是常量字符串,编译时会被放入只读数据段(属于静态区/常量区)
    • 它的完整内容是 "123456\0"(编译器自动添加结束符 '\0'),存储在静态区的只读区域,不可修改(强行修改会触发未定义行为,比如程序崩溃)。

⑥、关于 char s[] = "abc";

  1. 字符串字面量 "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  

原因

  • str1str2str3str4数组,每个数组独立分配内存(栈上),即使内容相同,地址不同,== 比较地址,结果为 0(假)。
  • str5str6str7str8指针,指向字符串字面量(存常量区,相同字面量共享地址 ),所以 str5str6str7str8 地址相同,== 结果为 1(真)。

我的答案: 0 0 0 0
我的原因: 不都是在栈区申请了的临时变量指针吗?
仔细思考: 指针说白了就是用来存储地址的,说白了str5 str6 和str7 str8都指向的是同一块地址,自然而然值相同,对于str1 str2 str3 str4 来说 char str1[] = “abc”; 存在栈区的str1 会把静态常量区的“abc”拷贝到栈区?

豆包给我的回答:
详细分析:

  1. str1str2 是数组(栈上独立内存)
  • char str1[] = "abc";char str2[] = "abc"; 分别在栈上创建两个独立的数组。
  • 虽然它们的内容都是 "abc\0",但数组名代表数组首地址,而两个数组在内存中的位置不同,因此 str1str2 的地址不同。
  • str1 == str2 比较的是地址,结果为 0(假)
  1. str3str4 同理(const 不影响存储位置)
  • const 仅限制对数组内容的修改,但数组本身仍在栈上分配独立内存。
  • 因此 str3str4 的地址不同,str3 == str4 结果为 0
  1. str5str6 是指针(指向静态区的同一字符串)
  • const char *str5 = "abc";str6 都指向静态区的字符串字面量 "abc\0"
  • 编译器通常会将相同的字符串字面量合并为一处存储,因此 str5str6 指向同一块内存地址。
  • str5 == str6 比较的是指针值(地址),结果为 1(真)
  1. str7str8 同理(即使没有 const,仍指向静态区)
  • 虽然 char *str7 = "abc"; 没有显式声明 const,但字符串字面量 "abc\0" 仍存于静态区且不可修改。
  • str7str8 同样指向静态区的同一字符串,因此 str7 == str8 结果为 1

关键区别:

  • 数组名(如 str1str2)代表数组首地址,每个数组在内存中独立分配,地址不同。
  • 指针(如 str5str6)存储的是字符串字面量的地址,相同的字面量通常共享同一内存位置。

因此,数组比较结果为 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;
}

错误及修正

  1. malloc 空间不足

    • 原代码 malloc(len) 未给字符串结束符 '\0' 留空间,需改为 malloc(len + 1)
  2. src[len] 语法错误

    • src 是指针,src[len] 是字符(越界,src 有效下标 0~len-1 ),应改为 src + len - 1(指向最后一个有效字符 )。
  3. 未处理字符串结束符

    • 倒序后需手动在 dest 末尾加 '\0',否则输出乱码。
  4. 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. 输出依次:0011(数组地址不同,常量区字符串指针地址相同 )。
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;
}

关键步骤说明:

  1. 构造指针数组 rowPtrs
    由于 printArr2D 的参数是 int**(指向指针的指针),而二维数组 arr2D 的行地址可以通过指针数组存储。
    rowPtrs[i] = arr2D[i]; 让每个指针元素指向 arr2D 的一行。

  2. 调用 printArr2D
    直接传递 rowPtrs(类型为 int**),匹配函数参数要求。

这样就能正确打印二维数组的内容,输出:

1 2 3 
4 5 6 

八、使用C语言实现void memecpy(void* pdest,void* psrc,size_t nsrc)

在这里插入图片描述

实现思路
memcpy 函数用于将一段内存区域的数据复制到另一个区域,需要处理以下关键点:

  1. 内存重叠问题:标准 memcpy 不保证处理重叠内存(需用 memmove),但题目要求实现 memcpy,因此忽略重叠。
  2. 字节级复制:使用 char* 指针逐字节复制,确保处理任意数据类型。
  3. 参数合法性检查:检查输入指针是否为 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;
}

关键说明

  1. 指针类型转换

    • void* 转换为 char* 以逐字节复制,确保正确处理任意数据类型。
    • src 被声明为 const char*,防止修改源数据。
  2. 内存重叠处理

    • 若源区域与目标区域重叠,结果未定义(如 srcdest 前且部分重叠,可能覆盖未复制的数据)。
    • 若需处理重叠,应使用 memmove(通过逆向复制避免覆盖)。
  3. 返回值

    • 返回 pdest 以便链式调用(如 strcpy(dest, memcpy(dest, src, n)))。
  4. 性能优化

    • 实际标准库实现可能使用更高效的方式(如按字长复制或 SIMD 指令),但此处采用简洁的逐字节复制以保证通用性。

此实现符合标准 memcpy 的行为,适用于非重叠内存的复制场景。


网站公告

今日签到

点亮在社区的每一天
去签到