相比于VC++ 6.0使用的ANSI C标准,VS2022使用的C11标准与上一代有很多不同,相比之前的 C 标准(如 C89/C90 和 C99),引入了一些新的功能、特性和改进。以下是 C11 标准相对于之前版本的一些主要变化和新增内容:
1.多线程支持:
在 C11 标准中引入了 <threads.h>
头文件,其中定义了一组函数和类型,用于支持线程相关的操作。这个头文件提供了一些函数和类型,可以用于创建、管理和同步线程。
以下是 <threads.h>
中常见的一些函数和类型:
-
类型定义:
thrd_t
:代表线程的类型。mtx_t
:代表互斥锁的类型。cnd_t
:代表条件变量的类型。
-
线程函数:
thrd_create
:创建一个新的线程。thrd_exit
:终止当前线程。thrd_join
:等待指定线程终止。thrd_detach
:设置线程为分离状态,使其在终止后自动释放资源。
-
互斥锁函数:
mtx_init
:初始化互斥锁。mtx_lock
:锁定互斥锁。mtx_unlock
:解锁互斥锁。mtx_destroy
:销毁互斥锁。
-
条件变量函数:
cnd_init
:初始化条件变量。cnd_signal
:发送信号给等待条件变量的线程。cnd_broadcast
:广播信号给所有等待条件变量的线程。cnd_wait
:等待条件变量变为真。
以下是一个示例用法:
#include <stdio.h>
#include <threads.h>
mtx_t mutex; // 互斥锁
int shared_data = 0; // 共享的数据
int increment_data(void* arg) {
mtx_lock(&mutex); // 锁定互斥锁
shared_data++; // 修改共享数据
mtx_unlock(&mutex); // 解锁互斥锁
return 0;
}
int main() {
thrd_t thread;
mtx_init(&mutex, mtx_plain); // 初始化互斥锁
// 创建线程
if (thrd_create(&thread, increment_data, NULL) != thrd_success) {
fprintf(stderr, "无法创建线程\n");
return 1;
}
// 等待线程终止
if (thrd_join(thread, NULL) != thrd_success) {
fprintf(stderr, "无法等待线程\n");
return 1;
}
printf("共享数据的值: %d\n", shared_data);
mtx_destroy(&mutex); // 销毁互斥锁
return 0;
}
2.泛型选择表达式:
_Generic
关键字是 C11 标准引入的一个新特性,用于根据表达式的类型选择不同的代码块。它主要用于实现泛型编程中的条件选择,允许编写更通用的代码,根据表达式的类型动态地选择不同的处理方式。
_Generic
关键字的基本语法形式如下:
_Generic(expression,
type1: code_block1,
type2: code_block2,
//...
default: default_code_block
)
其中:
expression
是要进行类型选择的表达式。type1
,type2
, ... 是不同的类型。code_block1
,code_block2
, ... 是与对应类型匹配的代码块。default
是可选的默认代码块。
下举一个简单的例子:
#include <stdio.h>
#define PrintValue(x) _Generic((x), \
int: printf("整数: %d\n", x), \
float: printf("浮点数: %f\n", x), \
double: printf("双精度浮点数: %lf\n", x), \
default: printf("其他类型\n") \
)
int main() {
int a = 10;
float b = 3.14f;
double c = 2.71828;
PrintValue(a); // 整数: 10
PrintValue(b); // 浮点数: 3.140000
PrintValue(c); // 双精度浮点数: 2.718280
char d = 'A';
PrintValue(d); // 其他类型
return 0;
}
在这个例子中,_Generic
根据传入参数的不同类型,选择不同的 printf
语句进行输出。如果传入的参数类型不在指定的类型范围内,则使用默认的输出方式。这样,可以根据表达式的类型,动态地选择不同的代码执行路径。
使用 _Generic
关键字可以编写更灵活和通用的代码,使得代码在编译期间就能进行类型检查和类型选择,提高了代码的可读性和通用性。
3.匿名结构体和联合体:
在 C11 标准中引入了匿名结构体和匿名联合体的概念,允许在不提供名称的情况下定义结构体和联合体,使其更为灵活。
匿名结构体
匿名结构体允许您定义一个没有标签名称的结构体。这种结构体可以直接在声明变量时定义,而无需指定结构体名称。它在某些情况下能够简化代码。
#include <stdio.h>
int main() {
struct { // 匿名结构体
int x;
int y;
} point; // 定义匿名结构体变量
point.x = 10;
point.y = 20;
printf("x 坐标: %d, y 坐标: %d\n", point.x, point.y);
return 0;
}
匿名联合体
匿名联合体与匿名结构体类似,允许定义没有标签名称的联合体。这种联合体也可以直接在声明变量时定义,而无需指定联合体名称。
#include <stdio.h>
int main() {
union { // 匿名联合体
int intValue;
float floatValue;
} data; // 定义匿名联合体变量
data.intValue = 42;
printf("整数值: %d\n", data.intValue);
data.floatValue = 3.14f;
printf("浮点数值: %f\n", data.floatValue);
return 0;
}
使用匿名结构体和匿名联合体时要注意,由于它们没有名称,因此无法在其他地方使用相同的定义。因此,这种方式适用于某些情况下仅需在局部范围内使用的结构体或联合体。
4.静态断言:
_Static_assert
是 C11 标准引入的一个关键字,用于在编译时执行静态断言(static assertion)。静态断言是指在编译时检查条件是否为真,如果条件为假则会触发编译器错误,防止程序继续编译。
_Static_assert(constant_expression, "error_message");
constant_expression
是在编译时期计算结果为常量的表达式。如果该表达式的值为 0(假),则会触发静态断言,编译器会显示指定的错误信息。
error_message
是一个字符串常量,表示在触发断言时显示的错误信息。
#include <stdio.h>
#define ARRAY_SIZE 10
_Static_assert(ARRAY_SIZE > 5, "数组大小必须大于 5");
int main() {
printf("程序正常运行\n");
return 0;
}
在上面的例子中,如果 ARRAY_SIZE
小于或等于 5,编译将会失败并显示 "数组大小必须大于 5" 的错误信息。这样的静态断言可用于在编译时期检查常量表达式的值,确保代码的正确性。
静态断言的使用可以在编译阶段发现潜在的错误,提高代码质量,并在一定程度上增强代码的可读性。通常用于检查各种常量表达式是否符合预期,并提前发现错误,避免将错误带入到运行时。
5.可重入的标准库函数:
C11 引入了一些可重入的标准库函数,这些函数可以在多线程环境下安全地被多个线程同时调用,而不会导致竞态条件或数据损坏。
可重入函数具有以下特点:
- 不会使用静态数据。
- 不会修改除了传入参数和返回结果以外的任何数据。
- 在多线程环境下可以安全地并发调用。
以下是一些 C11 标准中引入的可重入函数示例:
-
rand_r
:生成伪随机数的函数,传入一个状态变量,返回下一个伪随机数。通过传递不同的状态变量,可以在多线程环境下安全地使用这个函数。 -
gmtime_r
和localtime_r
:这两个函数用于将时间表示为日历时间,分别用于将时间戳转换为 UTC 时间和本地时间。这些函数的可重入版本使用了用户提供的结构体指针作为输出参数,而不是使用静态缓冲区。 -
asctime_r
、ctime_r
、strtok_r
:这些函数是asctime
、ctime
和strtok
的可重入版本。它们接收用户提供的缓冲区或状态指针,而不使用静态数据。 -
getenv
和putenv
:这两个函数是用于环境变量的操作,getenv_r
是getenv
的可重入版本,接收用户提供的缓冲区来存储环境变量的值。
这些可重入的标准库函数为多线程编程提供了更安全和可靠的基础,因为它们避免了使用静态数据,而是使用用户提供的参数来存储结果或中间状态。这样可以确保这些函数在多线程环境中能够安全地被调用,而不会产生竞态条件或数据损坏。
6.原子操作:
C11 标准引入了原子操作,提供了一组函数和类型,用于支持多线程并发访问共享变量时的原子性操作。原子操作是在不被中断的情况下执行的操作,保证了对共享变量的操作不会被其他线程的操作所干扰。
C11 中原子操作的函数位于 <stdatomic.h>
头文件中,并定义了一些原子类型和原子操作函数。以下是一些常见的原子操作函数和类型:
-
原子类型:
atomic_bool
,atomic_char
,atomic_int
,atomic_long
,atomic_llong
,atomic_short
,atomic_float
,atomic_double
,atomic_ptr
等,这些类型用于创建对应类型的原子变量。
-
原子加载和存储操作:
atomic_load
:从原子变量中加载值。atomic_store
:将值存储到原子变量。atomic_exchange
:交换原子变量的值,并返回原来的值。
-
原子递增和递减操作:
atomic_fetch_add
:将指定值加到原子变量上。atomic_fetch_sub
:将指定值从原子变量中减去。
-
原子比较和交换操作(CAS):
atomic_compare_exchange_strong
:如果原子变量的值等于给定的旧值,则用新值替换它。atomic_compare_exchange_weak
:类似于atomic_compare_exchange_strong
,但在某些情况下会使用弱型别的 CAS。
#include <stdio.h>
#include <stdatomic.h>
#include <threads.h>
atomic_int counter = ATOMIC_VAR_INIT(0); // 初始化原子变量
int increment_counter(void* arg) {
for (int i = 0; i < 10000; ++i) {
atomic_fetch_add(&counter, 1); // 原子递增操作
}
return 0;
}
int main() {
thrd_t thread1, thread2;
thrd_create(&thread1, increment_counter, NULL);
thrd_create(&thread2, increment_counter, NULL);
thrd_join(thread1, NULL);
thrd_join(thread2, NULL);
printf("Counter: %d\n", atomic_load(&counter)); // 输出原子变量的值
return 0;
}
这个示例演示了如何使用 C11 的原子操作来安全地递增一个共享的原子变量 counter
。原子操作确保了对共享变量的递增操作不会被其他线程的操作所干扰,避免了竞态条件。原子操作函数提供了一种安全而有效的方式来处理并发编程中的共享资源。
7.内存模型:
C11 引入了对 C 语言的内存模型的显式支持,定义了一些规则和保证,以便程序员更好地理解并发程序中的内存访问和同步。C11 内存模型描述了程序中各种内存访问操作的行为和规则。
C11 中的内存模型包含以下重要概念:
1.顺序一致性(Sequential Consistency):
C11 内存模型默认情况下提供了顺序一致性的保证。这意味着程序中的所有线程看到的内存操作都好像是按照一个全局的、全局顺序的序列来执行的。这种模型简化了多线程编程中对内存操作的理解。
2.原子类型和原子操作:
C11 引入了原子类型(例如 atomic_int
等)和原子操作函数(例如 atomic_load
、atomic_store
等),用于执行原子性操作,保证在多线程环境中对共享变量的操作是原子的。
3.内存顺序(Memory Orderings):
C11 提供了对内存操作的顺序控制,即通过指定适当的 memory order 来控制原子操作的顺序。比如 memory_order_relaxed
、memory_order_acquire
、memory_order_release
等。
4.原子性操作的同步语义:
使用原子操作函数可以实现一些同步语义,比如 release-acquire、acquire-release 和 sequentially-consistent。
5.内存屏障(Memory Barriers):
内存屏障是一种指令,用于强制内存操作的顺序和同步。C11 中使用 atomic_thread_fence
函数来实现内存屏障,它可以确保在其前后的内存操作的执行顺序。
C11 内存模型提供了一种明确定义多线程程序中对共享内存访问的行为方式,有助于程序员更好地理解和控制多线程程序中的内存访问。这对于编写并发程序,避免竞态条件(Race Conditions)、数据竞争(Data Races)以及其他并发问题是非常重要的。
8.Unicode支持改进:
C11 标准增强了对 Unicode 字符集的支持,以更好地处理和操作 Unicode 字符串和字符。这些改进包括一些新的数据类型、宏以及支持 Unicode 的字符串处理函数。
以下是 C11 中 Unicode 支持的一些改进:
1.char16_t
和 char32_t
类型:
引入了 char16_t
和 char32_t
类型,用于分别表示 UTF-16 和 UTF-32 编码的 Unicode 字符。这些类型提供了直接处理不同宽度的 Unicode 字符的机制。
2.u
前缀和 U
前缀:
C11 标准引入了 u
前缀表示 UTF-16 编码的字符串字面量,以及 U
前缀表示 UTF-32 编码的字符串字面量。例如,u"Hello"
表示一个 UTF-16 编码的字符串。
3.char16_t
和 char32_t
字符串处理函数:
C11 引入了一些新的 Unicode 字符串处理函数,如 u16len
、u32len
、u16tou32
、u32tou16
等,用于处理 char16_t
和 char32_t
类型的 Unicode 字符串。
4.<uchar.h>
头文件:
C11 引入了 <uchar.h>
头文件,其中包含了用于 Unicode 字符处理的新函数和宏,例如 c16rtomb
、c32rtomb
、mbrtoc16
、mbrtoc32
等函数。
这些改进使得 C11 更好地支持 Unicode 字符串和字符的处理,提供了更直接、更全面的机制来处理不同宽度的 Unicode 编码。这些改进有助于编写更具有国际化和多语言支持的程序,使得处理 Unicode 字符集更加方便和灵活。
9.新增的库函数:
引入了一些新的标准库函数,如 aligned_alloc
、fmax
, fmin
等,用于提供更多的数学和内存管理功能。
C11 标准引入了一些新的库函数,以增强 C 语言的功能性和标准库的支持。这些新函数提供了更多的功能,以适应新的需求和编程模式。以下是一些 C11 新增的库函数:
宽字符字符串处理函数:
wchar_t
类型的字符串处理函数:wcsncpy_s
、wcsncat_s
、wcsnlen
等函数,用于宽字符字符串的拷贝、连接和长度计算。
安全性增强函数:
安全性增强函数:例如 strcpy_s
、strncpy_s
、strcat_s
、strtok_s
等函数,用于字符串操作,可以避免缓冲区溢出问题。
快速数学运算函数:
快速数学运算函数:例如 sin
, cos
, tan
, exp
, log
, sqrt
等数学函数的 _FMA
、_FMAF
、_FMAL
版本,用于提高数学运算的性能。
aligned_alloc()
aligned_alloc
是 C11 引入的函数,用于分配内存并保证返回的内存地址满足特定的对齐要求。它的函数原型如下:
void *aligned_alloc(size_t alignment, size_t size);
alignment
参数是所需对齐的字节数,必须是 2 的幂且至少为 sizeof(void *)
的倍数。
size
参数是要分配的内存大小,以字节为单位。
aligned_alloc
函数会返回一个指向对齐内存块的指针。该函数可以用于需要特定内存对齐要求的情况,比如 SSE、AVX 等特定指令集的数据要求特定对齐方式的情况。
fmax()
, fmin()
fmax()
和 fmin()
是 C11 新增的数学函数,用于返回两个浮点数的最大值和最小值。fmax
返回两个参数中的最大值,fmin
返回两个参数中的最小值。
double fmax(double x, double y);
float fmaxf(float x, float y);
long double fmaxl(long double x, long double y);
这些新增的函数增强了 C 语言标准库的功能性和安全性,提供了更多的工具和功能,使得编程更为方便、高效和安全。这些函数能够更好地支持并发编程、Unicode 字符串处理、泛型编程和数学运算等方面。