How to Use typeof, Statement Expressions and Block-Scope Label Names
This article gives an overview of the following C-language extensions (part of the GNU C-implementation) introduced in the Oracle Developer Studio C compiler. Although these extensions are not part of the latest ISO C99 standard, they are supported by the popular gcc compilers.
- The typeof keyword which allows references to an arbitrary type
- Statement expressions that make it possible to specify declarations and statements in expressions
- Block-scope label names
The article also demonstrates how to use the new C compiler features for creating generic macros on example of linked-list manipulation-routines. Such macros semantically mimic C++ Standard Template Library, support arbitrary data types and provide strict compile-time type-checking.
This article is organized into the following sections:
Introducing the typeof Keyword
The typeof keyword is a new extension to the C language. The Oracle Developer Studio C compiler accepts constructs with typeof wherever a typedef name is accepted, including the following syntactic categories:
- Declarations
- Parameter type lists and return types in a function declarator
- Type definitions
- Cast operators
- The sizeof operators
- Compound literals
- The typeof argument
The compiler accepts this keyword in combination with double underscores: __typeof , __typeof__ . The examples in this article do not make use of the double underscore convention. Syntactically, the typeof keyword is followed by parentheses which contain either the name of a type or an expression. This is similar to the possible operands which are accepted by the sizeof keyword (unlike sizeof , bit-fields are allowed as typeof argument what is interpreted as corresponding integer type). Semantically, the typeof keyword acts like the name of a type (typedef name) and specifies a type.
Example Declarations That Use typeof
The following are two equivalent declarations for the variable a of type int .
The following example shows declarations of pointers and arrays. To compare, equivalent declarations without typeof are also given.
If you use an expression with typeof , the expression is not evaluated. Only the type of that expression is derived. The following example declares the variable var of type int because the expression foo() is of type int . The function foo is not invoked because the expression is not evaluated.
Restrictions of Declarations That Use typeof
Note that the type name in a typeof construct cannot contain storage-class specifiers such as extern or static . However, type qualifiers such as const or volatile are allowed. For example, the following code is invalid because it declares extern in the typeof construct:
The following declaration of the identifier b with external linkage denotes an object of type int and is valid. The next declaration is also valid and declares a const qualified pointer of type char which means the pointer p cannot be modified.
Using typeof in Macro Definitions
The main application of typeof constructs is probably in macro definitions. You can use the typeof keyword to refer to the type of a macro parameter. Consequently, it is possible to construct objects with necessary types without specifying the type names explicitly as macro arguments.
The FINDMAX Macro
The FINDMAX macro finds the largest value in an array. The types of the elements can be int , float or double .
The FINDMAX macro requires three arguments:
- m : a variable (or other lvalue) that is set to the maximum value found
- x : an array where the search is performed
- n : the number of elements in the array
Note that after argument substitution, the expressions which are specified as macro parameters are evaluated only once. That is achieved by employing two local variables: _x , _n . Consider the following macro invocation:
After macro substitution, the for loop looks like this:
Without local copies of macro parameters, they would be evaluated in every iteration of the for loop which is wrong:
An implementation of the FINDMAX macro which does not use the typeof keyword somehow needs to specify the types of the local variables. For example, the types could be specified by adding another macro parameter for the name of a type or by providing three versions of that macro, each for a different type. For creating generalized macros both of these solutions are not as flexible as a solution using typeof .
Note the declaration of _x :
Assuming x is either a pointer to the initial element of an array or an array, this instruction declares _x as a pointer to the elements of the array. Here typeof argument is an expression, which is not evaluated, that designates an element of the array to refer to its type.
A declaration such as
would work only for pointers because typeof(x) , in this instance x is an array, is still an array type. No implicit conversion of «array of type» to «pointer to type» is performed for a typeof argument and an array cannot be initialized with another array.
Running Code That Uses FINDMAX
The following is a sample application which incorporates the FINDMAX macro:
This program produces
Notice the confusing way in which the FINDMAX macro returns the results. Using FINDMAX looks like a function call but the results are returned through one of the arguments of the function macro and that argument is not a pointer (a function cannot change the value of its argument by changing the value of the parameter). A compound statement, which we use in the macro definition to limit the scope of the local variables, can not return a value. However, statement expressions can. That is the second language extension to be discussed in this article.
Using Statement Expressions
A compound statement enclosed in parentheses represents statement expression. The simplest statement expression is just empty statement expression which has type void
On one hand, statement expressions, like compound statements, can contain other statements as well as nested statement expressions, declarations and, again like compound statements, open a new scope. For example, the following expression contains declarations of two automatic int variables, not visible outside the scope of the expression, and two expression statements that assign value 1 to both a and b :
On the other hand, statement expressions, like expressions, have a type, compute a value and can be used wherever expressions are accepted:
- Declarations, initializers
- Expression statements, expressions
- Selection ( if , switch ) and iterations ( while , do , for ) statements, the return statements
One exception to this is that statement expressions have to be in scope within a function or a block, no file scope statement expressions are allowed. The following program is incorrect because statement expression <( 1; )>is used outside a function.
If a statement expression ends with an expression, the type and value are determined by the latter. In other cases, the type of the expression is void and there is no value. A statement expression has type void if the last statement is not an expression that returns a value. The first statement expression in the next example is of type void because it ends with the declaration.
It is a valid expression unless it is used as an rvalue. The second expression is of type int and evaluated to the value 1 because it ends with expression statement a=1; where a is int .
With the help of statement expressions our generalized FINDMAX macro can be rewritten in the following manner:
Now the macro does not require parameter m to return the result because the last statement of the statement expression is used to do this and after macro substitution FINDMAX becomes an expression and its usage syntactically looks like calling a function:
Declaring Local Labels
Another language extension that is useful for writing macros is local labels support. According to the C standard a label identifier has function scope. It is impossible to define two labels with the same name within one function despite of block scopes they may be defined. Using regular function-scope labels in macros is restricted, as multiple invocations of the same macro in one function will result in multiple definitions of the same label. The new keyword __label__ limits visibility of labels within a block scope. Such a declaration begins with __label__ keyword followed by one or more label identifiers separated by commas. For example:
Local labels must be declared at the beginning of a block (a statement expression also opens a block) or a function prior to any statements. Local labels are defined in the usual way. In the following example two local labels are defined with the same name in different blocks within function foo .
Local labels may be convenient for complex macros where using goto is reasonable. Although FINDMAX macro is not complex enough to require goto ‘s it is rewritten to demonstrate how local labels can be used. In the following version of FINDMAX a local label is declared and defined in a statement expression:
Using local label avoids possible conflicts with label names because label l is local to the enclosing statement expression and is not visible outside the expression.
Implementing a List Using typeof and Statement Expressions
List Declaration
Let us implement a set of macros for working with a list data structure. As a requirement assume we need some sort of type generic macros that allow constructing different types of lists so that the same macros can be used with lists of ints, floats or any arbitrary types. Let us first write a macro for a list declaration.
The TLIST declares a trivial double linked list named L . The list is strictly typed because every element of the list has type T and compiler will check type compatibility during references to node element _data . Moreover, the nodes of different lists have different types so they cannot be used together in one macro. Structure _List##L represents a node of the list. The list itself is a single node that is used to mark the end of the list and it is circularly linked. The other nodes will be allocated dynamically. Initializer in the declaration ensures that the list is initially empty. For example:
This example declares list l1 of integer numbers and a list l2 of structures. The other advantage of declaring such a macro (instead of a typeless declaration), is the ease of debugging. We can inspect the elements of the list without using type casts.
There are two limitations of the TLIST macro definition. Firstly, it does not allow us to declare a list of pointers to functions although this is easily overcome by replacing T with typeof(T) in the macro definition (for a pointer to void function returning void , the declaration would look like TLIST(void (*)(void), l) ). Secondly, although we can declare a list of arrays (in the same way , using typeof(T), the macro TLISTINSERT shown below would not be valid. This is because in C we cannot assign one array to another array or to initialize array with another array. It could be done using memcpy function , but then we will lose type checking because parameters of memcpy are void pointers. The workaround for arrays is to wrap them in a structure.
Insertion/Removal Elements of List
Two main operations on a list are implemented here: insertion of an element and removal of an element.
The macro TLISTINSERT inserts a copy of object V into the list before the node I . The macro allocates memory for new node dynamically using malloc and copies object V into the new node. Pointer to that node is returned. The type of object V has to match the type specified during list declaration. TLISTERASE removes node I from the list and frees previously allocated memory for the node. The macro TLISTERASE returns pointer to the node following removed node. Necessary macros TLISTBEGIN and TLISTEND return pointers to the first and the next after the last node in the list L respectively. If a list L is empty then TLISTBEGIN(L) == TLISTEND(L) . Two macros TLISTPUSHFRONT and TLISTPUSHBACK are shorter notation for insertion of a new element before the first and after the last elements of list correspondingly. Now it is possible to fill a list as shown below:
List l1 will contain two elements in order 71660 and 71451 . An example, which also uses C99 compound literals, of filling a list of structures:
List l2 will contain two elements <71660, "Mar">and <71451, "Apr">. And if we try to insert an element of one type into the list declared to contain elements of other incompatible type, compiler will detect an error, as for the next code fragment:
Moreover it is a compile-time check so there is no run-time penalty in our implementation of a strictly typed list.
Again to note, the arguments of TLISTINSERT and TLISTERASE macros are evaluated only once what enables writing nested constructs (without typeof and statement expressions it would be quite difficult and cumbersome):
The list l3 will contain three elements in order «Jan» , «Feb» and «Mar» .
List Iterations
The next important step is to define means to traverse the lists. An idea of pointers to nodes as iterators fits well.
The TLISTITER allows declare pointers to the nodes (iterators) of list L . TLISTINC returns pointer to the next node after the node I and TLISTREF is used to dereference a pointer to the node to access the corresponding element of the list.
Now we can define an utility macro for emptying the whole list:
Macro TLISTCLEAR frees memory allocated for nodes of the list L , which becomes empty.
As you can see, macros TLISTITER , TLISTINC and TLISTREF allow to use the following syntax to traverse lists
Although lists l1 and l2 are of different types, the same macros are used here. And if we confuse pointers to nodes of different lists, as in the next example, compiler will emit a warning about different pointer types:
Logically, we can provide shorter notation of enumerating elements of a list by defining macro TLISTFOREACH
It requires three arguments. I is the name of pointer to current node in the list L so list items could be referenced in the blk. L is name of the list and blk is a block to be executed for every list element.
Now printing content of lists l1 and l2 can be rewritten:
It looks shorter that the original version with for loops.
Also using TLISTFOREACH we can easily define a macro to determine size of a list as follow
TLISTSIZE returns number of elements in the list L.
Some more words about parameter blk of the TLISTFOREACH macro. It is intended to be a statement expression and determines operations performed for every list item. It might be a compound statement, but in the following case C preprocessor will detect an error:
There is a problem here because comma operator will be treated as a separator of parameters of macro. To avoid this, the comma operator should be screened inside matched pairs of parenthesis . And a statement expression has a natural pair of them.
Going further, we define another generic algorithm on lists — finding a list element:
where I — is the name of pointer to current node in the list L. Parameter blk should be an statement expression or just expression that is evaluated to non zero when required element has been found in the list. Iteration stops when blk is evaluated to nonzero value and macro TLISTFIND returns pointer to the current node. If there is no such an element in the list, TLISTFIND returns a pointer to the end of list. The next code demonstrates how TLISTFIND macro can be used:
Analogous to definition of macro TLISTFIND other generic algorithms can be defined on the lists.
Three example programs are shown below that demonstrate the usage of macros defined thus far.
Русские Блоги
Ключевое слово Typeof на языке c
Следующие два эквивалентных объявления используются для объявления переменной a класса int
typeof(int) a;
typeof (‘b’) a; // эквивалентно получению типа ‘b’ и определению переменной a
Следующее используется для объявления указателей и массивов
typeof(int*) p1,p2;
typeof (p1) p2 // p1 и p2 имеют одинаковый тип
int *p1,*p2;
typeof(int [10]) a1,a2;
int a1[10],a2[10];
Если для выражения используется typeof, выражение не будет выполнено, будет получен только тип выражения. В следующем примере объявляется переменная var типа int, поскольку выражение foo () Он имеет тип int, потому что выражение не будет выполнено, поэтому нет необходимости вызывать foo ()
char *str1 = "123";
int my_atoi(const char *str)
typeof (my_atoi (str2)) sum = 100; // эквивалентно int sum;
typeof (my_atoi ()) sum = 100; // неправильное использование, функция с параметрами должна быть приведена в фактические параметры
Использовать ограничения объявления typeof
Имя типа в конструкции typeof не может содержать спецификаторы класса хранения, такие как
extern или static.
, например, const или volatile. Например, следующий код недействителен. Потому что он объявляет extern в конструкции typeof;
typeof(extern int) a;
Следующий код использует внешнюю ссылку, чтобы объявить, что идентификатор b действителен и представляет объект типа int. Также допустимо следующее объявление, которое объявляет указатель типа char с использованием квалификатора const , Указывая, что указатель p не может быть изменен.
char *str1 = "123";
extern typeof(int) b;
typeof (char * const) p = str1; // указатель константы, указатель указателя нельзя изменить; константный тип const должен быть инициализирован при объявлении
Использовать typeof в объявлении макроса
Основное применение конструкции typeof используется в определении макроса. Вы можете использовать ключевое слово typeof для ссылки на тип макропараметров.
Если вы программист C ++, вы должны были связатьсяC++Оператор decltype в 11 используется для автоматического вывода типа данных выражений для решения проблемы, которую некоторые типы в универсальном программировании трудно (или даже невозможно) выразить из-за параметров шаблона. На самом деле, эта функция также давно реализована на языке C. Функция расширения typeof (PS: не typedef) в стандарте GNU C. похожа на decltype. Давайте посмотрим, как использовать это ключевое слово.
Давайте сначала посмотрим на простейший пример:
- // demo 01
- int var = 666;
- typeof(int *) pvar = &var;
- printf("pvar:\t%p\n", pvar);
- printf("&var:\t%p\n", &var);
- printf("var:\t%d\n", var);
- printf("*pvar:\t%d\n", *pvar);
Сначала мы определяем переменную типа int для переменной var, а затем определяем переменную типа указателя для указания на переменную. Обычно мы напрямую используем int * xxx = & xx, но мы не хотим быть такими прямыми, чтобы продемонстрировать использование typeof, typeof автоматически выводится после ), Поэтому typeof (int *) напрямую получает тип int *, объявляет pvar с этим типом и инициализирует его по адресу var, вывод должен быть очевидным, это на моей машине выход:
Что ж, я признаю, что приведенный выше пример нелегок, а написание int * просто и понятно. Чтобы сделать его таким неясным, нужно добавить typeof. Фактически, эффект typeof заключается в том, что он может автоматически выводить тип выражения. Int *) изменено на typeof (& var), оно также будет автоматически выводить тип типа & var-int *, вы можете попробовать это сами, принцип тот же, в этом случае, когда мы сталкиваемся с очень сложным выражением, мы очень Когда трудно определить его тип, typeof полезен. Еще один момент, на который следует обратить внимание: typeof — это уникальное расширение в стандарте GNU C. Это стандартное ISO C не имеет этого ключевого слова, поэтому вы не можете добавлять какие-либо стандартные параметры ISO C во время компиляции, в противном случае вы получите ошибку, такую как компиляция приведенного выше кода. Добавлена опция -std = c90, компилятор выдаст кучу ошибок:

Решение простое: замените -std = c90 на -std = gnu90, что является стандартом GNU.
Вот еще несколько примеров, таких как
- // demo 02
- int *pvar = NULL;
- typeof(*pvar) var = 999;
- printf("var:\t%d\n", var);
Этот пример сначала определяет целочисленную переменную-указатель pvar, выражение в скобках после typeof равно * pvar, а тип pvar — int *, затем * pvar, конечно, анализируется как Тип int, поэтому переменная var, объявленная с этим типом, также является типом int, что эквивалентно int var = 999; результат будет следующим:

- // demo 03
- int *pvar = NULL;
- typeof(*pvar) var[4] = <11, 22, 33, 44>;
- for (int i = 0; i < 4; i++)
- printf("var[%d]:\t%d\n", i, var[i]);
Тип, анализируемый typeof в этот раз, совпадает с предыдущим, разница в том, что var — это массив из четырех элементов, что эквивалентно int var [4] = <. >; вывод выглядит следующим образом:

На этот раз немного горизонтально:
- // demo 04
- typeof(typeof(const char *)[4]) pchar = <"hello", "world", "good", "night">;
- for (int i = 0; i < 4; i++)
- printf("pchar[%d]:\t%s\n", i, pchar[i]);
На этот раз все выглядит сложнее, пришло время проверить ваши навыки работы с указателями, он вложил два слоя в typeof, мы разбили слой за слоем, сначала посмотрим на самый внутренний слой, typeof сначала анализирует тип const char *, опытные программисты на C должны сразу же подумать о строке, за которой следует [4], указывающий, что это тип массива, содержащий четыре строки, затем Этот тип анализируется самым внешним типомof, тогда последний тип pchar — это тип, который эквивалентен const char * pchar [4] = <. >;
Давайте снова проверим ваш указатель, на этот раз это указатель на функцию:
- // demo 05
- int add(int param1, int param2) <
- return param1 + param2;
- >
- int sub(int param1, int param2) <
- return param1 — param2;
- >
- int mul(int param1, int param2) <
- return param1 * param2;
- >
- int main() <
- int (*func[3]) (int, int) =
; - typeof(func[0](1, 1)) sum = 100;
- typeof(func[1](1, 1)) dif = 101;
- typeof(func[2](1, 1)) pro = 102;
- printf("sum:\t%d\n", sum);
- printf("dif:\t%d\n", dif);
- printf("pro:\t%d\n", pro);
- return 0;
- >
В этой демонстрации сначала определяются три функции. Все эти три функции возвращают значение типа int и принимают два параметра типа int, а затем определяют функцию в основной функции. Указатель на массив func и его инициализация с указанными выше тремя именами функций, затем мы рассмотрим тип, под которым будет получен первый тип type.Func [0] ссылается на функцию add, за которой следуют два параметра в скобках, Простой вызов add (1, 1) и add вернет значение типа int, поэтому последний выводимый тип — int, два других — одинаковые, поэтому sum, dif, pro на самом деле три Целые числа эквивалентны int sum = 100; int dif = 101; int pro = 102; хорошо, я допускаю, что это демо немного изъято, и этот пример неуместно цитируется, а выходные результаты — их соответствующие значения:

Давайте посмотрим на его применение в определении макроса:
- // demo 06
- #define pointer(T) typeof(T *)
- #define array(T, N) typeof(T[N])
- int main() <
- array(pointer(char), 4) pchar = <"hello", "world", "good", "night">;
- for (int i = 0; i < 4; i++)
- printf("pchar[%d]:\t%s\n", i, pchar[i]);
- return 0;
- >
Здесь используются макро-функции, указатель (T) будет заменен на typeof (T *), что означает, что за указателем следует имя определенного типа, которое станет Используйте typeof для анализа типа указателя соответствующего типа, и после массива следует имя типа и целое число, а затем typeof будет проанализирован как массив этого типа, так что массив (pointer (char), 4), указатель (char) в главной функции Сначала он будет проанализирован как тип char *, а затем внешний массив будет проанализирован как тип массива, содержащий 4 элемента char *, поэтому он эквивалентен char * pchar [4] = <. >; выходные данные выглядят следующим образом:

Хорошо, так много слов, ключевое слово typeof наконец знает, что делать, кажется, что синтаксис довольно неясен, и нет никакого практического использования, тогда хорошо, позвольте мне взглянуть на один из реальных проектов примеры:
- /*
- * Выбран из исходного кода ядра linux-2.6.7
- * filename: linux-2.6.7/include/linux/kernel.h
- */
- #define min(x,y) ( < \
- typeof(x) _x = (x); \
- typeof(y) _y = (y); \
- (void) (&_x == &_y); \
- _x < _y ? _x : _y; >)
Приведенный выше пример выбирается из заголовочного файла include / linux / kernel.h в ядре linux 2.6.7. Функция определения макроопределения min заключается в выборе наименьшего из двух объектов одного типа. Он принимает два параметра x и y. , Следующая часть замены макроса использует typeof, чтобы определить две переменные _x и _y, и назначить значения для xy соответственно. Цель typeof здесь состоит в том, чтобы позволить min принимать параметр любого типа без ограничения одним типом, который немного Общие запахи программирования, последнее выражение _x <_y? _X: _y; использует условный оператор для возврата наименьшего из двух значений; в середине есть предложение (void) (& _x == & _y); Это бессмыслица, на самом деле это предложение имеет специальное назначение, потому что мы не можем гарантировать, что два параметра, которые вы передаете, когда используете min, одного типа, тогда вам нужно выполнить тест, а язык C не поддерживает прямой typeof (_x) == typeof (_y), поэтому возьмите адрес и сравните с типом указателя. Если типы двух указателей несовместимы, компилятор сгенерирует предупреждение для достижения эффекта обнаружения Что касается предыдущего (void), поскольку только выражение & _x == & _y само по себе не имеет смысла, то без этого (void) компилятор также предупредит: оператор без эффекта [-Wunused-value], недопустимый оператор, Если вы не хотите видеть это предупреждение, добавьте (void) впереди, чтобы игнорировать его.
Персональный сайт Crifan:http://www.crifan.com/order_min__macro_definition_void_amp__x__amp__y_the_meaning_of/
C# check type
In this article we show how to check types in C# with typeof and is operators, and the GetType method.
In C#, every variable and expression has a type. A type is a set of values and the allowable operations on those values.
- storage space that a variable of the type requires
- maximum and minimum values of the type
- type members such as fields or methods
- base type from which it inherits
- implemented interfaces
- allowable operations
The compiler uses these information to ensure type-safe operations.
The System.Type class represents type declarations.
We can check type with typeof operator, is operator, or GetType method. The typeof operator obtains the System.Type instance for a type. The operator checks the type at compile time. It only works on types, not variables.
The GetType method gets the type of the current object instance. It checks the type at runtime.
The is operator checks if an instance is in the type’s inheritance tree. In addition, it allows type-safe casting.
Advertisements C# typeof example
The first example uses the typeof operator to print the System.Type for built-int types.
The example prints System.Type for int, List<int>, string, double, float, decimal and User.
C# GetType example
The following example checks the types of three variables.
We define an integer, string, and decimal variables. We use the GetType method to check the types at runtime.
Advertisements C# is operator
The is operator checks if an object is compatible with a given type, i.e. it is in its inheritance tree.
We create two objects from user defined types.
We have a Base and a Derived class. The Derived class inherits from the Base class.
Base equals Base and so the first line prints True. The Base is also compatible with Object type. This is because each class inherits from the mother of all classes — the Object class.
The derived object is compatible with the Base class because it explicitly inherits from the Base class. On the other hand, the _base object has nothing to do with the Derived class.
C# type-safe checking with is
We can perform type-safe casting with the is operator.
We have an array of objects. For all integers, we calculate its power.
If the conversion is possible, the is operator creates a local variable val of int type.
Alternatively, we can use the LINQ’s OfType method for the job.
The OfType method filters the elements of an IEnumerable based on a specified type.
Advertisements C# check boxed value
With the is operator, we can check the actual type of a boxed value. Boxing is the process of converting a value type to the type object .
The example determines the actual type of two boxed values.
C# switch expression type pattern
Types can be patterns to the switch expression.
In the example, we check the types of variables using switch expression.
In this article we showed how to check types in C# with typeof , is , and GetType .
typeof(T) vs. TypeOf⟨T⟩
Иногда рефлексивные вызовы дороги в терминах производительности и не могут быть опущены.
В этой статье представлены паттерны, позволяющие существенно повысить производительность множественных рефлексивных вызовов посредством техники виртуализации (кэширования) результатов.

Рассмотрим следующий метод:
Важный паттерн в подобных случаях — это кэширование локальной переменной в качестве статической, например:
Но как поступить в случае обобщённого (generic) метода?
Существует практичное обобщённое решение, взгляните.
*Примечательно, что до момента добавления обобщённых классов и методов, C# уже имел поддержку ряда обобщённых операторов: `typeof`, `is`, `as`
Что насчёт другого сценария?
TypeOf.cs
* Примечание: как указали в комментариях, для более надёжной потокобезопасности нужно использовать ConcurrentDictionary (это может повлиять на результаты тестов)
Итак, теперь можно использовать:
Что насчёт недостатков `TypeOf` паттерна?
* `typeof(List<>)` допустимо
* `TypeOf<List<>>` не допустимо