GCC Attribute Syntax

本文详细解析了C语言中属性规范的语法与使用场景,包括函数、变量、类型、标签和枚举器等的属性应用,以及GCC编译器中特定的属性用法。

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

This section describes the syntax with which __attribute__ may be used, and the constructs to which attribute specifiers bind, for the C language. Some details may vary for C++ and Objective-C. Because of infelicities in the grammar for attributes, some forms described here may not be successfully parsed in all cases.

There are some problems with the semantics of attributes in C++. For example, there are no manglings for attributes, although they may affect code generation, so problems may arise when attributed types are used in conjunction with templates or overloading. Similarly, typeid does not distinguish between types with different attributes. Support for attributes in C++ may be restricted in future to attributes on declarations only, but not on nested declarators.

See Function Attributes, for details of the semantics of attributes applying to functions. See Variable Attributes, for details of the semantics of attributes applying to variables. See Type Attributes, for details of the semantics of attributes applying to structure, union and enumerated types. See Label Attributes, for details of the semantics of attributes applying to labels. See Enumerator Attributes, for details of the semantics of attributes applying to enumerators. See Statement Attributes, for details of the semantics of attributes applying to statements.

An attribute specifier is of the form __attribute__ ((attribute-list)). An attribute list is a possibly empty comma-separated sequence of attributes, where each attribute is one of the following:

  • Empty. Empty attributes are ignored.
  • An attribute name (which may be an identifier such as unused, or a reserved word such as const).
  • An attribute name followed by a parenthesized list of parameters for the attribute. These parameters take one of the following forms:
    • An identifier. For example, mode attributes use this form.
    • An identifier followed by a comma and a non-empty comma-separated list of expressions. For example, format attributes use this form.
    • A possibly empty comma-separated list of expressions. For example, format_arg attributes use this form with the list being a single integer constant expression, and alias attributes use this form with the list being a single string constant.

An attribute specifier list is a sequence of one or more attribute specifiers, not separated by any other tokens.

You may optionally specify attribute names with ‘__’ preceding and following the name. This allows you to use them in header files without being concerned about a possible macro of the same name. For example, you may use the attribute name __noreturn__ instead of noreturn.

Label Attributes

In GNU C, an attribute specifier list may appear after the colon following a label, other than a case or default label. GNU C++ only permits attributes on labels if the attribute specifier is immediately followed by a semicolon (i.e., the label applies to an empty statement). If the semicolon is missing, C++ label attributes are ambiguous, as it is permissible for a declaration, which could begin with an attribute list, to be labelled in C++. Declarations cannot be labelled in C90 or C99, so the ambiguity does not arise there.

Enumerator Attributes

In GNU C, an attribute specifier list may appear as part of an enumerator. The attribute goes after the enumeration constant, before =, if present. The optional attribute in the enumerator appertains to the enumeration constant. It is not possible to place the attribute after the constant expression, if present.

Statement Attributes

In GNU C, an attribute specifier list may appear as part of a null statement. The attribute goes before the semicolon.

Type Attributes

An attribute specifier list may appear as part of a struct, union or enum specifier. It may go either immediately after the struct, union or enum keyword, or after the closing brace. The former syntax is preferred. Where attribute specifiers follow the closing brace, they are considered to relate to the structure, union or enumerated type defined, not to any enclosing declaration the type specifier appears in, and the type defined is not complete until after the attribute specifiers.

All other attributes

Otherwise, an attribute specifier appears as part of a declaration, counting declarations of unnamed parameters and type names, and relates to that declaration (which may be nested in another declaration, for example in the case of a parameter declaration), or to a particular declarator within a declaration. Where an attribute specifier is applied to a parameter declared as a function or an array, it should apply to the function or array rather than the pointer to which the parameter is implicitly converted, but this is not yet correctly implemented.

Any list of specifiers and qualifiers at the start of a declaration may contain attribute specifiers, whether or not such a list may in that context contain storage class specifiers. (Some attributes, however, are essentially in the nature of storage class specifiers, and only make sense where storage class specifiers may be used; for example, section.) There is one necessary limitation to this syntax: the first old-style parameter declaration in a function definition cannot begin with an attribute specifier, because such an attribute applies to the function instead by syntax described below (which, however, is not yet implemented in this case). In some other cases, attribute specifiers are permitted by this grammar but not yet supported by the compiler. All attribute specifiers in this place relate to the declaration as a whole. In the obsolescent usage where a type of int is implied by the absence of type specifiers, such a list of specifiers and qualifiers may be an attribute specifier list with no other specifiers or qualifiers.

At present, the first parameter in a function prototype must have some type specifier that is not an attribute specifier; this resolves an ambiguity in the interpretation of void f(int (__attribute__((foo)) x)), but is subject to change. At present, if the parentheses of a function declarator contain only attributes then those attributes are ignored, rather than yielding an error or warning or implying a single parameter of type int, but this is subject to change.

An attribute specifier list may appear immediately before a declarator (other than the first) in a comma-separated list of declarators in a declaration of more than one identifier using a single list of specifiers and qualifiers. Such attribute specifiers apply only to the identifier before whose declarator they appear. For example, in

__attribute__((noreturn)) void d0 (void),
    __attribute__((format(printf, 1, 2))) d1 (const char *, ...),
     d2 (void);

the noreturn attribute applies to all the functions declared; the format attribute only applies to d1.

An attribute specifier list may appear immediately before the comma, = or semicolon terminating the declaration of an identifier other than a function definition. Such attribute specifiers apply to the declared object or function. Where an assembler name for an object or function is specified (see Asm Labels), the attribute must follow the asm specification.

An attribute specifier list may, in future, be permitted to appear after the declarator in a function definition (before any old-style parameter declarations or the function body).

Attribute specifiers may be mixed with type qualifiers appearing inside the [] of a parameter array declarator, in the C99 construct by which such qualifiers are applied to the pointer to which the array is implicitly converted. Such attribute specifiers apply to the pointer, not to the array, but at present this is not implemented and they are ignored.

An attribute specifier list may appear at the start of a nested declarator. At present, there are some limitations in this usage: the attributes correctly apply to the declarator, but for most individual attributes the semantics this implies are not implemented. When attribute specifiers follow the * of a pointer declarator, they may be mixed with any type qualifiers present. The following describes the formal semantics of this syntax. It makes the most sense if you are familiar with the formal specification of declarators in the ISO C standard.

Consider (as in C99 subclause 6.7.5 paragraph 4) a declaration T D1, where T contains declaration specifiers that specify a type Type (such as int) and D1 is a declarator that contains an identifier ident. The type specified for ident for derived declarators whose type does not include an attribute specifier is as in the ISO C standard.

If D1 has the form ( attribute-specifier-list D ), and the declaration T D specifies the type “derived-declarator-type-list Type” for ident, then T D1 specifies the type “derived-declarator-type-list attribute-specifier-list Type” for ident.

If D1 has the form * type-qualifier-and-attribute-specifier-list D, and the declaration T D specifies the type “derived-declarator-type-list Type” for ident, then T D1 specifies the type “derived-declarator-type-list type-qualifier-and-attribute-specifier-list pointer to Type” for ident.

For example,

void (__attribute__((noreturn)) ****f) (void);

specifies the type “pointer to pointer to pointer to pointer to non-returning function returning void”. As another example,

char *__attribute__((aligned(8))) *f;

specifies the type “pointer to 8-byte-aligned pointer to char”. Note again that this does not work with most attributes; for example, the usage of ‘aligned’ and ‘noreturn’ attributes given above is not yet supported.

For compatibility with existing code written for compiler versions that did not implement attributes on nested declarators, some laxity is allowed in the placing of attributes. If an attribute that only applies to types is applied to a declaration, it is treated as applying to the type of that declaration. If an attribute that only applies to declarations is applied to the type of a declaration, it is treated as applying to that declaration; and, for compatibility with code placing the attributes immediately before the identifier declared, such an attribute applied to a function return type is treated as applying to the function type, and such an attribute applied to an array element type is treated as applying to the array type. If an attribute that only applies to function types is applied to a pointer-to-function type, it is treated as applying to the pointer target type; if such an attribute is applied to a function return type that is not a pointer-to-function type, it is treated as applying to the function type.

<think>我们正在讨论的是C/C++中的volatile关键字附近的语法错误。用户可能在嵌入式系统或MCU编程中遇到了这个问题。 在C/C++中,volatile关键字用于告诉编译器不要优化对该变量的访问,因为它可能在程序之外被改变(如硬件寄存器、中断服务程序等)。 语法错误通常是由于在错误的位置使用volatile或缺少必要的语法元素(如分号、括号等)造成的。 常见的错误情况包括: 1. 在变量声明或定义时,volatile的位置错误。 2. 在结构体或联合体定义中,volatile使用不当。 3. 在指针声明中,volatile的位置歧义(如const volatile和volatile const的区别以及指针本身的volatile和指向数据的volatile)。 4. 缺少分号导致后续的volatile关键字被错误解析。 下面我们将详细分析这些情况,并提供修正方法。 ### 1. volatile关键字的基本用法 volatile关键字通常用于变量声明,格式如下: ```c volatile int myVar; ``` 或者 ```c int volatile myVar; // 两种写法都可以 ``` 它们都表示`myVar`是一个易变的整型变量。 ### 2. 常见错误及修正 #### 错误1:缺少分号导致后续volatile被错误解析 例如: ```c int a = 5 // 缺少分号 volatile int b; ``` 编译器会报错,因为第一行缺少分号,导致第二行的`volatile`被解析为前一行的一部分。修正:在第一行末尾加上分号。 #### 错误2:在结构体中使用volatile时缺少分号或括号 例如: ```c typedef struct { volatile uint8_t reg1; // 正确 volatile uint8_t reg2 // 缺少分号 volatile uint8_t reg3; } MyRegisters; ``` 错误在于`reg2`后面缺少分号。修正:在`reg2`声明后加上分号。 #### 错误3:指针声明中的volatile位置歧义 volatile在指针声明中有两种含义: - 指向volatile数据的指针:`volatile int *p;` - volatile指针本身:`int * volatile p;` 也可以两者都是volatile:`volatile int * volatile p;` 如果写错顺序,可能导致语法错误或不符合预期。例如: ```c volatile int* p, q; // p是指向volatile int的指针,q是volatile int(普通变量) ``` 如果希望p和q都是指针,应该写成: ```c volatile int *p, *q; ``` 或者更清晰: ```c typedef volatile int * volatile_ptr; volatile_ptr p, q; ``` #### 错误4:在函数参数中使用volatile时缺少逗号或括号 例如: ```c void myFunction(volatile int a volatile int b) { // 错误,参数之间缺少逗号 } ``` 修正: ```c void myFunction(volatile int a, volatile int b) { } ``` #### 错误5:在数组声明中错误放置volatile 例如: ```c volatile int array[10]; // 整个数组是volatile的 int volatile array[10]; // 同上,正确 ``` 但是,如果写成: ```c int array volatile[10]; // 错误:volatile不能放在数组名字后面 ``` 修正:将volatile移到类型前面或后面(但必须在数组名之前)。 ### 3. 示例代码 以下是一个常见的嵌入式系统中的用法,定义一个指向硬件寄存器的volatile指针: ```c #define HW_REG (*(volatile uint32_t *)0x12345678) ``` 这里,0x12345678是硬件寄存器的地址,我们将其转换为指向volatile uint32_t的指针,然后解引用。 如果写错了,比如: ```c #define HW_REG (*(volatile uint32_t *)0x12345678) // 正确 #define HW_REG (volatile uint32_t * 0x12345678) // 错误:缺少解引用,且类型不匹配 ``` ### 4. 如何调试语法错误 - 检查报错行以及前一行是否有明显的语法错误(如缺少分号、括号不匹配等)。 - 如果错误信息指向volatile关键字,检查volatile的位置是否符合C/C++语法。 - 使用代码编辑器的语法高亮功能,有时可以直观地发现问题。 - 将复杂的声明分解,使用typedef简化。 例如,复杂的指针声明: ```c volatile uint8_t * volatile p; ``` 可以分解为: ```c typedef volatile uint8_t v_uint8_t; v_uint8_t * volatile p; ``` ### 5. 总结 volatile关键字附近的语法错误通常是由于上下文中的其他错误(如缺少分号、括号等)或volatile本身使用位置不当造成的。仔细检查代码,特别是volatile出现的前后行,通常可以快速定位问题。 在嵌入式编程中,正确使用volatile非常重要,否则可能导致程序行为异常(如编译器优化掉必要的读取操作)。因此,在修正语法错误后,还需确保语义正确。 §§相关问题§§ 1. volatile关键字在嵌入式编程中的具体作用是什么? 2. 如何区分const和volatile在指针声明中的位置? 3. 在中断服务程序中,为什么需要使用volatile变量? 4. 编译器优化可能会对未使用volatile的硬件寄存器访问造成什么影响?</think>### 分析:C/C++中`volatile`关键字附近的语法错误 在嵌入式系统或MCU编程中,`volatile`关键字用于防止编译器对变量进行优化,确保每次访问都从内存读取(常用于硬件寄存器或中断共享变量)。以下是常见语法错误及解决方案: --- ### 一、常见错误场景及修复方案 #### 1. **错误位置放置`volatile`** ```c // 错误示例 int volatile * ptr; // 正确但易混淆 volatile int* ptr; // 推荐写法 // 错误:volatile在变量名后 int* volatile ptr; // 表示指针本身是volatile,而非指向的数据 ``` **修复**: ```c volatile uint32_t *REG = (volatile uint32_t*)0x40000000; // 指向volatile数据的指针 ``` #### 2. **结构体/联合体中缺少分号** ```c // 错误示例(结构体成员缺少分号) typedef struct { volatile uint8_t status; // 正确 volatile uint8_t control // 错误!缺少分号 volatile uint32_t data; // 报错行 } DeviceReg; ``` **修复**: ```c typedef struct { volatile uint8_t status; // ✅ volatile uint8_t control; // ✅ 添加分号 volatile uint32_t data; } DeviceReg; ``` #### 3. **宏定义中未正确括号化** ```c // 错误示例 #define READ_REG(addr) volatile uint32_t* reg = addr; *reg // 使用时报错:volatile附近语法错误 uint32_t val = READ_REG(0x40000000); ``` **修复**: ```c #define READ_REG(addr) (*(volatile uint32_t*)(addr)) // ✅ 正确括号化 ``` #### 4. **中断服务程序中的错误** ```c // 错误示例 volatile bool flag = false; void __attribute__((interrupt)) ISR() { flag = true; // 正确 volatile int x = 10; // 正确但冗余 int volatile y = 20; // 语法正确但意义不明 } ``` --- ### 二、正确使用`volatile`的规范 #### 1. **基本规则** | 场景 | 正确用法 | 错误用法 | |------|----------|----------| | 硬件寄存器 | `volatile uint32_t *REG;` | `uint32_t volatile* REG;` | | 多线程共享变量 | `volatile int shared;` | `int shared volatile;` | | 结构体成员 | `struct { volatile int flag; }` | `struct { int volatile flag; }` | #### 2. **指针类型详解** ```c volatile int *p1; // 指向的数据是volatile int *volatile p2; // 指针本身是volatile volatile int *volatile p3; // 指针和数据都是volatile ``` --- ### 三、调试技巧 1. **编译器选项**:启用严格警告 GCC: `-Wall -Wextra -pedantic` 2. **错误定位**: 若报错`syntax error near 'volatile'`: - 检查前一行是否缺少分号 - 确认`volatile`是否在类型修饰符之后(如`unsigned volatile`错误) 3. **预处理检查**: 使用`gcc -E`查看宏展开后的代码 --- ### 四、示例:MCU寄存器访问 ```c // 正确用法 #define GPIO_A_MODER (*(volatile uint32_t*)0x40020000) void main() { GPIO_A_MODER |= 0x01; // 直接操作寄存器 while(1) { // 防止循环被优化掉 volatile int i = 1000000; while(i--); } } ``` > **关键点**: > - 硬件寄存器必须用`volatile` > - 延迟循环变量建议用`volatile` > - 指针转换需显式括号化:`(*(volatile TYPE*)addr)`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值