C语言-void/void *

本文详细解析了C语言中void关键字的使用,包括其在函数返回类型和参数类型中的作用,以及void*指针的特性与应用。了解void在不同场景下的正确使用,能帮助程序员避免常见的编译错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

void
  c语言中,void为“不确定类型”,不可以用void来声明变量。如:void a = 10;如果出现这样语句编译器会报错:variable or field ‘a’ declared void。
  在C语言中void 常常用于:对函数返回类型的限定和对函数参数限定  
  (1)对函数返回类型的限定:当函数不需要返回类型是必须用void 来限定返回类型,限定了函数的返回类型为void后函数不能有返回值;如:void fun(int a);
  (2)对函数参数类型的限定:当函数不允许接受参数时必须用void 来限定函数参数,限定了函数的参数类型为void后函数不能有参数;如:int fun(void);
  (3)没有返回类型的函数默认返回类型是什么?编译器默认返回int类型,如果不需要返回值写为void,这样在编译时就能检查出异常。

void * 
  C语言中void * 为 “不确定类型指针”,void *可以用来声明指针。如:void * a;
  (1)void *可以指向任何类型的指针类型:
    void *a = NULL;
    int * b = NULL;
    a  =  b
  (2)void *可以赋值给任何类型的变量 但是需要进行强制转换:
    int * a = NULL ;
    void * b ;
    a  =  (int *)b;
  需要注意的是:void* 在转换为其他数据类型时,赋值给void* 的类型和目标类型须保持一致,否则可能不能准确的访问到变量。原因是a指针指向的是b指针,把b指针保存的地址给了a,这样的话a指针指向一片内存的首地址,由于a指针保持的数据类型是int,所以在取值访问时会取4个字节数据,如果强转为其他类型,那么取值就是类型的字节数。

  应用:
    malloc返回的是void *类型,所以会在前面加类型强制转换。

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "adc.h" #include "dma.h" #include "tim.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ /* System interrupt init*/ /* SysTick_IRQn interrupt configuration */ NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),15, 0)); /** Disable the internal Pull-Up in Dead Battery pins of UCPD peripheral */ /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ void SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); MX_ADC1_Init(); MX_ADC2_Init(); MX_TIM1_Init(); MX_TIM2_Init(); MX_TIM3_Init(); MX_TIM4_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ 把SystemClock_Config()改成void SystemClock_Config()就编译成功了
最新发布
08-02
<think>我们正在讨论的是函数声明中参数列表的差异:`void SystemClock_Config(void)` 与 `void SystemClock_Config()` 在C语言中,这两者是有区别的: 1. `void SystemClock_Config(void)`:这是一个显式声明函数没有参数。在调用时,如果尝试传递任何参数,编译器会报错。 2. `void SystemClock_Config()`:这是一个旧式的函数声明(K&R风格),表示函数参数列表未指定(即可以接受任意参数或没有参数)。在C语言中,这种声明方式在C89标准中是允许的,但在C99及以后的标准中,这种声明方式被废弃,因为它不提供参数类型检查。 然而,在某些编译环境下(特别是较老的编译器或某些嵌入式编译环境),如果函数声明为`void SystemClock_Config()`,而定义却是`void SystemClock_Config(void)`,或者反过来,可能会导致链接错误。因为编译器可能会将它们视为不同的函数签名。 但是,为什么将声明从`void SystemClock_Config(void);`改为`void SystemClock_Config();`有时能解决编译问题呢? 原因可能如下: 1. **函数定义与声明不一致**:如果函数定义是旧式风格(即定义时没有指定参数列表),而声明使用了`void`,那么编译器可能会认为它们是不同的函数。例如: 定义可能是这样的: void SystemClock_Config() { // 函数体 } 而声明是: void SystemClock_Config(void); 在链接时,由于声明和定义的签名不匹配(一个为无参数,一个为未指定参数),链接器可能会认为这是两个不同的函数,从而报出未定义的符号错误。 2. **解决方式**:将声明改为`void SystemClock_Config();`,使其与定义匹配(旧式风格),那么链接器就能正确找到函数。 然而,在现代C语言编程中,我们应该避免使用旧式函数声明。正确的做法是: - 在声明和定义中都使用显式的参数列表,即使用`void`表示无参数。 - 如果函数确实没有参数,那么使用`void`是明确的,并且可以避免潜在的错误。 所以,如果遇到因为这种声明方式导致的链接错误,更推荐的做法是检查函数定义,并将其改为: void SystemClock_Config(void) 同时,声明也保持为: void SystemClock_Config(void); 这样,声明和定义就一致了,而且符合现代C标准。 但是,在某些情况下,如果函数定义是旧式风格(可能是历史遗留代码),而我们无法修改其定义,那么将声明改为旧式风格(即去掉`void`)可以临时解决问题。但这并不是推荐的做法,因为它会失去参数检查。 总结: - 使用`void SystemClock_Config(void)`是明确且安全的。 - 使用`void SystemClock_Config()`是过时的,并且可能导致问题。 - 如果因为声明和定义不一致导致链接错误,应该统一使用`void`的显式声明,或者将定义改为显式声明(推荐)。如果无法修改定义,才考虑将声明改为旧式风格(不推荐)。 注意:在C++中,这两种写法都是表示无参数,即`void SystemClock_Config()`等同于`void SystemClock_Config(void)`。但是C语言中,它们有区别。 因此,在C语言中,为了解决链接错误,我们应优先确保声明和定义完全一致(包括参数列表的写法)。如果定义是旧式风格,那么声明也要用旧式风格;如果定义是`void`,那么声明也要用`void`。但为了代码的健壮性和可维护性,建议将所有无参函数都使用`void`显式声明。 根据引用[1]和[2]中的代码片段,它们使用的都是现代C语言风格(使用了显式的参数类型),因此,在STM32的标准外设库或HAL库中,通常使用`void`来显式声明无参数函数。所以,如果出现链接错误,更可能是由于函数定义缺失或者函数定义与声明不一致(比如定义被条件编译排除)等原因。 因此,用户提到的将声明改为`void SystemClock_Config()`能解决编译问题,可能是因为函数定义恰好是旧式风格(没有使用`void`)或者定义在另一个文件中以旧式风格出现。但这不是根本的解决办法,根本办法是统一使用显式的`void`并确保定义存在且正确包含在工程中。 引用[1]中出现的函数定义并没有直接给出,但引用[2]中的函数`SetSysClock`是静态函数,并且使用了显式的参数列表(无参数,但写法是`void`?实际上引用[2]中的函数定义是:`static void SetSysClock(void)`吗?我们看引用[2]的代码片段中,函数定义是`static void SetSysClock(void)`吗?实际上,在引用[2]中,我们看到: static void SetSysClock(void) 但是,在给出的代码片段中,并没有完整的参数列表,我们只能看到函数名后面没有参数。但是,在C语言中,函数定义如果没有写参数列表,那就是旧式风格。而引用[2]的代码片段中,函数名后面是跟着`void`还是空的?我们看: static void SetSysClock(void) 在代码片段中,我们看到的是: static void SetSysClock(void) 因为代码片段中第一行是:`static void SetSysClock(void) {`,所以它是显式声明了无参数。 因此,在引用[2]中,函数定义使用的是`void`,所以声明也应该使用`void`。 所以,用户的问题中,如果出现链接错误,将声明从`void SystemClock_Config(void);`改为`void SystemClock_Config();`可能只是临时避开了因为声明和定义不一致(比如定义是旧式风格)的问题。但更可能的情况是,用户并没有提供函数定义,或者函数定义没有被正确编译。而修改声明后,编译器可能不再严格检查参数,从而在链接时通过(但实际函数定义仍然缺失,所以不太可能)。因此,这种修改通常不是解决链接错误的正确方法。 正确的解决步骤应该是: 1. 确保函数`SystemClock_Config`在某个源文件中有定义(即实现),并且该源文件被包含在工程中。 2. 确保函数的声明(在头文件中)和定义(在源文件中)的签名完全一致(都使用`void`)。 3. 如果定义是旧式风格(无参数列表),则建议修改为显式的`void`。 如果用户确实遇到了将声明改为旧式风格后链接错误消失的情况,那么请检查函数定义是否是旧式风格(即定义时写的是`void SystemClock_Config()`,注意:这里没有`void`在括号内,但这是不允许的,因为C语言中函数定义不允许省略参数列表,除非是旧式风格,即没有参数类型声明)。实际上,在C99及以后,函数定义必须指定参数类型,无参数必须用`void`。 因此,在标准C中,函数定义不允许使用`void SystemClock_Config()`这种形式(即空括号)来定义无参数函数。所以,用户的问题可能是由于其他原因导致的,而修改声明可能只是巧合(比如同时修改了其他内容,或者重新编译后解决了)。 结论:不推荐使用`void SystemClock_Config()`这种声明方式,而应该使用`void SystemClock_Config(void)`。链接错误的原因通常是定义缺失或声明与定义不匹配(包括函数名、参数列表、返回类型),所以应该从这些方面去检查。 如果用户坚持使用旧式声明,那么可能会带来以下风险: - 编译器不会检查参数,因此如果调用时不小心传递了参数,编译器不会报错,这可能导致运行时错误。 因此,我们建议用户按照以下步骤解决: 1. 在声明和定义中都使用`void`作为参数列表。 2. 确保函数定义被正确包含在工程中。 3. 如果函数定义在另一个文件中,确保该文件被编译(在Keil工程中,该.c文件被添加到工程中)。 注意:在引用[1]中,我们看到用户添加了UART相关的配置,但是并没有提供完整的`SystemClock_Config`函数,所以需要用户确认该函数是否在工程中存在。 最后,如果用户确实在某个文件中找到了函数定义,但定义是旧式风格(例如:`void SystemClock_Config()`),那么为了兼容性,声明可以改为旧式风格,但强烈建议将定义改为`void SystemClock_Config(void)`。 由于在C99及以后,空参数列表的函数声明已被废弃,所以最好避免使用。 所以,用户的问题“将函数声明从 void SystemClock_Config(void); 改为 void SystemClock_Config() 为何能解决编译问题”的答案是:可能是因为函数定义是旧式风格(无参数列表),或者定义在另一个文件中以旧式风格声明,但更可能是巧合(比如同时修改后重新编译,而之前的问题已经解决)。从长远来看,应该统一使用显式的`void`声明和定义。</think>在C语言中,将函数声明从 `void SystemClock_Config(void);` 改为 `void SystemClock_Config();` **本质上不能解决编译问题**,但在某些特定情况下可能暂时规避错误。以下是关键差异和潜在影响分析: ### 1. 语法差异解析 | 声明形式 | 语义 | 标准符合性 | 参数检查 | |---------|------|-----------|---------| | `void SystemClock_Config(void)` | 显式声明**无参数** | C99/C11标准 | 严格检查调用时不能带参数 | | `void SystemClock_Config()` | 隐式声明**参数未指定** | K&R旧式标准 | 不检查参数(可能接受任意参数) | ### 2. 为何有时"能解决"编译问题(临时规避) 当出现以下特殊情况时,修改声明可能暂时消除错误: 1. **定义与声明不匹配** 若函数定义是旧式风格(如 `void SystemClock_Config() {...}`),而声明用 `(void)` 会导致签名不一致。改为空括号使声明与定义匹配。 2. **编译器严格模式差异** 某些编译器(如旧版Keil ARMCC)在宽松模式下可能接受空括号声明,但严格模式下要求显式 `(void)`。 3. **参数传递误用** 如果代码中存在错误调用(如 `SystemClock_Config(0)`),`(void)`声明会立即报错,而空括号可能暂时通过编译(但会导致运行时错误)。 ### 3. 根本解决方案(推荐) ```mermaid graph TD A[链接错误L6218E] --> B[检查函数定义是否存在] B -->|存在| C[统一声明与定义签名] C --> D[两者均使用 void func(void)] B -->|不存在| E[添加函数实现] E --> F[确认包含在工程中] ``` 正确做法应是: 1. **保持声明与定义一致**(推荐显式声明) ```c // 头文件声明 void SystemClock_Config(void); // 源文件定义 void SystemClock_Config(void) { // STM32时钟配置代码 RCC_OscInitTypeDef RCC_OscInitStruct = {0}; // ... [^1][^2] } ``` 2. **解决未定义符号的核心方法**- ✅ 将实现文件(如 `system_stm32g4xx.c`)加入工程 - ✅ 移除函数定义的 `static` 限定符 - ✅ 检查头文件包含路径 -**避免**使用空括号这种不规范的声明方式 ### 4. 特殊案例说明 在引用[1]的代码中,存在外设时钟配置: ```c PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1; PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit); // 需要正确定义的SystemClock_Config[^1] ``` 若 `SystemClock_Config` 未正确定义,无论声明如何修改,链接阶段仍会失败。 > **关键结论**:修改 `(void)` 为 `()` 不是真正的解决方案,只是可能掩盖声明/定义不匹配的问题。坚持使用显式 `void` 声明能提高代码健壮性和可移植性[^1][^2][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值