Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C cast a block type into another got unexpected result

typedef (void (^blockType)());

I need cast blocks with different argument types into a same type blockType, and invoke it as the original type later. But there is a issue while casting block type.

The following code works well with any argument type, ...

((blockType)^(BOOL b) {
    NSLog(@"BOOL: %d", b);
})(YES); // >> BOOL: 1
((blockType)^(int i) {
    NSLog(@"int: %d", i);
})(1); // >> int: 1
((blockType)^(double f) {
    NSLog(@"double: %f", f);
})(1.0 / 3); // >> double: 0.333333
((blockType)^(NSString *s) {
    NSLog(@"NSString *: %@", @"string");
})(1.0 / 3); // >> NSString *: string

except float:

((blockType)^(float f) {
    NSLog(@"float: %f", f);
})(1.0f); // >> float: 0.000000
((blockType)^(float f) {
    NSLog(@"float: %f", f);
})(1.0f / 3); // >> float: 36893488147419103232.000000

but it is ok without casting:

(^(float f) {
    NSLog(@"float without casting: %f", f);
})(1.0 / 3); // >> float without casting: 0.333333

how to explain and resolve it?

like image 788
Mr. Míng Avatar asked Jan 07 '17 02:01

Mr. Míng


1 Answers

It appears to be a taint of the good old C language. Consider the following code (we can say it is kind of 'translation' of your Obj-C block with issues to C as far as blocks are related to function pointers (see here)):

void test()
{
    void (*pEmpty)();
    pEmpty = functionFloat;
    pEmpty(1.0f / 3);
}

void functionFloat(float f)
{
    printf("float: %f", f);
}

If you call test you will see the same result as when you invoke your 'sick' block. Compiler will provide just a warning about incompatible pointers and will let you run. But if you change

void (*pEmpty)();

to

void (*pEmpty)(void);

there will be a compile-time error. Same will happen if you add void explicitly to your void-blocks, e.g. (void (^)(void) instead of (void (^)().

The reason for such behavior explained in the C Standard:

The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.
§6.7.6.3-14 (p.134)

Thus, as it doesn't mean that there are no parameters but rather no info about them, cast passes fine.

The problem with unexpected output is the following:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.
§6.3.2.3-8 (p.56)

and

If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.
§6.5.2.2-9 (p.82)

So, it seems that the solution here is just like @jtbandes said: don't mess block types and re-design this part of code to avoid such casts.

like image 107
degapps Avatar answered Oct 14 '22 21:10

degapps